114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton/* 214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * Copyright 2018 The Android Open Source Project 314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * 414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * Licensed under the Apache License, Version 2.0 (the "License"); 514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * you may not use this file except in compliance with the License. 614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * You may obtain a copy of the License at 714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * 814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * http://www.apache.org/licenses/LICENSE-2.0 914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * 1014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * Unless required by applicable law or agreed to in writing, software 1114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * distributed under the License is distributed on an "AS IS" BASIS, 1214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * See the License for the specific language governing permissions and 1414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton * limitations under the License. 1514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton */ 1614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 1714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonpackage androidx.webkit; 1814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 1914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport static org.junit.Assert.assertEquals; 20610473e5bb1295c992f8be92311bbfad96f03311Gustav Senntonimport static org.junit.Assume.assumeTrue; 2114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 2214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport android.support.test.filters.MediumTest; 2314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport android.support.test.runner.AndroidJUnit4; 2414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport android.webkit.JavascriptInterface; 2514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport android.webkit.WebResourceRequest; 2614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport android.webkit.WebResourceResponse; 2714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport android.webkit.WebView; 2814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 2914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport org.junit.After; 3014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport org.junit.Before; 3114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport org.junit.Test; 3214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport org.junit.runner.RunWith; 3314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 3414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport java.io.ByteArrayInputStream; 3514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport java.util.ArrayList; 3614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport java.util.List; 3714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonimport java.util.concurrent.Callable; 3814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 3914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton@MediumTest 4014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton@RunWith(AndroidJUnit4.class) 4114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Senntonpublic class ServiceWorkerClientCompatTest { 4214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 4314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // The BASE_URL does not matter since the tests will intercept the load, but it should be https 4414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // for the Service Worker registration to succeed. 4514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static final String BASE_URL = "https://www.example.com/"; 4614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static final String INDEX_URL = BASE_URL + "index.html"; 4714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static final String SW_URL = BASE_URL + "sw.js"; 4814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static final String FETCH_URL = BASE_URL + "fetch.html"; 4914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 5014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static final String JS_INTERFACE_NAME = "Android"; 5114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static final int POLLING_TIMEOUT = 10 * 1000; 5214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 5314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // static HTML page always injected instead of the url loaded. 5414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static final String INDEX_RAW_HTML = 5514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton "<!DOCTYPE html>\n" 5614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + "<html>\n" 5714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " <body>\n" 5814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " <script>\n" 5914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " navigator.serviceWorker.register('sw.js').then(function(reg) {\n" 6014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " " + JS_INTERFACE_NAME + ".registrationSuccess();\n" 6114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " }).catch(function(err) {\n" 6214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " console.error(err);\n" 6314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " });\n" 6414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " </script>\n" 6514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " </body>\n" 6614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + "</html>\n"; 6714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static final String SW_RAW_HTML = "fetch('fetch.html');"; 6814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static final String SW_UNREGISTER_RAW_JS = 6914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton "navigator.serviceWorker.getRegistration().then(function(r) {" 7014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " r.unregister().then(function(success) {" 7114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " if (success) " + JS_INTERFACE_NAME + ".unregisterSuccess();" 7214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " else console.error('unregister() was not successful');" 7314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " });" 7414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + "}).catch(function(err) {" 7514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + " console.error(err);" 7614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton + "});"; 7714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 7814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private JavascriptStatusReceiver mJavascriptStatusReceiver; 7914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private WebViewOnUiThread mOnUiThread; 8014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 8114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // Both this test and WebViewOnUiThread need to override some of the methods on WebViewClient, 8214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // so this test subclasses the WebViewClient from WebViewOnUiThread. 8314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private static class InterceptClient extends WebViewOnUiThread.WaitForLoadedClient { 8414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 8514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton InterceptClient(WebViewOnUiThread webViewOnUiThread) throws Exception { 8614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton super(webViewOnUiThread); 8714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 8814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 8914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @Override 9014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public WebResourceResponse shouldInterceptRequest(WebView view, 9114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton WebResourceRequest request) { 9214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // Only return content for INDEX_URL, deny all other requests. 9314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton try { 9414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton if (request.getUrl().toString().equals(INDEX_URL)) { 9514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton return new WebResourceResponse("text/html", "utf-8", 9614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton new ByteArrayInputStream(INDEX_RAW_HTML.getBytes("UTF-8"))); 9714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 9814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } catch (java.io.UnsupportedEncodingException e) { } 9914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton return new WebResourceResponse("text/html", "UTF-8", null); 10014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 10114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 10214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 10314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public static class InterceptServiceWorkerClient extends ServiceWorkerClientCompat { 10414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton private List<WebResourceRequest> mInterceptedRequests = new ArrayList<WebResourceRequest>(); 10514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 10614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @Override 10714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) { 10814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // Records intercepted requests and only return content for SW_URL. 10914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mInterceptedRequests.add(request); 11014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton try { 11114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton if (request.getUrl().toString().equals(SW_URL)) { 11214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton return new WebResourceResponse("application/javascript", "utf-8", 11314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton new ByteArrayInputStream(SW_RAW_HTML.getBytes("UTF-8"))); 11414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 11514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } catch (java.io.UnsupportedEncodingException e) { } 11614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton return new WebResourceResponse("text/html", "UTF-8", null); 11714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 11814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 11914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton List<WebResourceRequest> getInterceptedRequests() { 12014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton return mInterceptedRequests; 12114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 12214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 12314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 12414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @Before 12514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public void setUp() throws Exception { 12614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mOnUiThread = new WebViewOnUiThread(); 12714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mOnUiThread.getSettings().setJavaScriptEnabled(true); 12814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 12914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mJavascriptStatusReceiver = new JavascriptStatusReceiver(); 13014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mOnUiThread.addJavascriptInterface(mJavascriptStatusReceiver, JS_INTERFACE_NAME); 13114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mOnUiThread.setWebViewClient(new InterceptClient(mOnUiThread)); 13214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 13314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 13414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @After 13514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public void tearDown() throws Exception { 13614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton if (mOnUiThread != null) { 13714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mOnUiThread.cleanUp(); 13814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 13914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 14014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 14114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // Test correct invocation of shouldInterceptRequest for Service Workers. 14214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @Test 14314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public void testServiceWorkerClientInterceptCallback() throws Exception { 144610473e5bb1295c992f8be92311bbfad96f03311Gustav Sennton assumeTrue(WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_BASIC_USAGE)); 145610473e5bb1295c992f8be92311bbfad96f03311Gustav Sennton assumeTrue(WebViewFeature.isFeatureSupported( 146610473e5bb1295c992f8be92311bbfad96f03311Gustav Sennton WebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST)); 14714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 14814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton final InterceptServiceWorkerClient mInterceptServiceWorkerClient = 14914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton new InterceptServiceWorkerClient(); 15014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton ServiceWorkerControllerCompat swController = ServiceWorkerControllerCompat.getInstance(); 15114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton swController.setServiceWorkerClient(mInterceptServiceWorkerClient); 15214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 15314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mOnUiThread.loadUrlAndWaitForCompletion(INDEX_URL); 15414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 15514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton Callable<Boolean> registrationSuccess = new Callable<Boolean>() { 15614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @Override 15714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public Boolean call() { 15814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton return mJavascriptStatusReceiver.mRegistrationSuccess; 15914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 16014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton }; 16114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton PollingCheck.check("JS could not register Service Worker", POLLING_TIMEOUT, 16214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton registrationSuccess); 16314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 16414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton Callable<Boolean> receivedRequest = new Callable<Boolean>() { 16514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @Override 16614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public Boolean call() { 16714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton return mInterceptServiceWorkerClient.getInterceptedRequests().size() >= 2; 16814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 16914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton }; 17014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton PollingCheck.check("Service Worker intercept callbacks not invoked", POLLING_TIMEOUT, 17114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton receivedRequest); 17214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 17314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton List<WebResourceRequest> requests = mInterceptServiceWorkerClient.getInterceptedRequests(); 17414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton assertEquals(2, requests.size()); 17514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton assertEquals(SW_URL, requests.get(0).getUrl().toString()); 17614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton assertEquals(FETCH_URL, requests.get(1).getUrl().toString()); 17714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 17814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // Clean-up, make sure to unregister the Service Worker. 17914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mOnUiThread.evaluateJavascript(SW_UNREGISTER_RAW_JS, null); 18014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton Callable<Boolean> unregisterSuccess = new Callable<Boolean>() { 18114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @Override 18214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public Boolean call() { 18314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton return mJavascriptStatusReceiver.mUnregisterSuccess; 18414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 18514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton }; 18614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton PollingCheck.check("JS could not unregister Service Worker", POLLING_TIMEOUT, 18714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton unregisterSuccess); 18814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 18914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 19014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // Object added to the page via AddJavascriptInterface() that is used by the test Javascript to 19114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton // notify back to Java if the Service Worker registration was successful. 19214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public static final class JavascriptStatusReceiver { 19314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public volatile boolean mRegistrationSuccess = false; 19414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public volatile boolean mUnregisterSuccess = false; 19514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 19614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @JavascriptInterface 19714b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public void registrationSuccess() { 19814b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mRegistrationSuccess = true; 19914b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 20014b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton 20114b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton @JavascriptInterface 20214b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton public void unregisterSuccess() { 20314b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton mUnregisterSuccess = true; 20414b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 20514b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton } 20614b9f252b74caf73f6a2967722a465f075b3bc1eGustav Sennton} 207