xref: /aosp_15_r20/cts/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2016 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 static org.junit.Assert.assertEquals;
20 
21 import android.webkit.JavascriptInterface;
22 import android.webkit.ServiceWorkerClient;
23 import android.webkit.ServiceWorkerController;
24 import android.webkit.WebResourceRequest;
25 import android.webkit.WebResourceResponse;
26 import android.webkit.WebView;
27 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
28 
29 import androidx.test.ext.junit.rules.ActivityScenarioRule;
30 import androidx.test.ext.junit.runners.AndroidJUnit4;
31 import androidx.test.filters.MediumTest;
32 
33 import com.android.compatibility.common.util.NullWebViewUtils;
34 import com.android.compatibility.common.util.PollingCheck;
35 
36 import org.junit.After;
37 import org.junit.Assume;
38 import org.junit.Before;
39 import org.junit.Rule;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 
43 import java.io.ByteArrayInputStream;
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.concurrent.Callable;
47 
48 @MediumTest
49 @RunWith(AndroidJUnit4.class)
50 public class ServiceWorkerClientTest extends SharedWebViewTest {
51 
52     // The BASE_URL does not matter since the tests will intercept the load, but it should be https
53     // for the Service Worker registration to succeed.
54     private static final String BASE_URL = "https://www.example.com/";
55     private static final String INDEX_URL = BASE_URL + "index.html";
56     private static final String SW_URL = BASE_URL + "sw.js";
57     private static final String FETCH_URL = BASE_URL + "fetch.html";
58 
59     private static final String JS_INTERFACE_NAME = "Android";
60     private static final int POLLING_TIMEOUT = 60 * 1000;
61 
62     // static HTML page always injected instead of the url loaded.
63     private static final String INDEX_RAW_HTML =
64             "<!DOCTYPE html>\n"
65             + "<html>\n"
66             + "  <body>\n"
67             + "    <script>\n"
68             + "      navigator.serviceWorker.register('sw.js').then(function(reg) {\n"
69             + "         " + JS_INTERFACE_NAME + ".registrationSuccess();\n"
70             + "      }).catch(function(err) {\n"
71             + "         console.error(err);\n"
72             + "      });\n"
73             + "    </script>\n"
74             + "  </body>\n"
75             + "</html>\n";
76     private static final String SW_RAW_HTML = "fetch('fetch.html');";
77     private static final String SW_UNREGISTER_RAW_JS =
78             "navigator.serviceWorker.getRegistration().then(function(r) {"
79             + "  r.unregister().then(function(success) {"
80             + "    if (success) " + JS_INTERFACE_NAME + ".unregisterSuccess();"
81             + "    else console.error('unregister() was not successful');"
82             + "  });"
83             + "}).catch(function(err) {"
84             + "   console.error(err);"
85             + "});";
86 
87     @Rule
88     public ActivityScenarioRule mActivityScenarioRule =
89             new ActivityScenarioRule(WebViewCtsActivity.class);
90 
91     private JavascriptStatusReceiver mJavascriptStatusReceiver;
92     private WebViewOnUiThread mOnUiThread;
93 
94     // Both this test and WebViewOnUiThread need to override some of the methods on WebViewClient,
95     // so this test subclasses the WebViewClient from WebViewOnUiThread.
96     private static class InterceptClient extends WaitForLoadedClient {
97 
InterceptClient(WebViewOnUiThread webViewOnUiThread)98         public InterceptClient(WebViewOnUiThread webViewOnUiThread) throws Exception {
99             super(webViewOnUiThread);
100         }
101 
102         @Override
shouldInterceptRequest(WebView view, WebResourceRequest request)103         public WebResourceResponse shouldInterceptRequest(WebView view,
104                 WebResourceRequest request) {
105             // Only return content for INDEX_URL, deny all other requests.
106             try {
107                 if (request.getUrl().toString().equals(INDEX_URL)) {
108                     return new WebResourceResponse("text/html", "utf-8",
109                         new ByteArrayInputStream(INDEX_RAW_HTML.getBytes("UTF-8")));
110                 }
111             } catch(java.io.UnsupportedEncodingException e) {}
112             return new WebResourceResponse("text/html", "UTF-8", null);
113         }
114     }
115 
116     public static class InterceptServiceWorkerClient extends ServiceWorkerClient {
117         private List<WebResourceRequest> mInterceptedRequests = new ArrayList<WebResourceRequest>();
118 
119         @Override
shouldInterceptRequest(WebResourceRequest request)120         public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
121             // Records intercepted requests and only return content for SW_URL.
122             mInterceptedRequests.add(request);
123             try {
124                 if (request.getUrl().toString().equals(SW_URL)) {
125                     return new WebResourceResponse("application/javascript", "utf-8",
126                         new ByteArrayInputStream(SW_RAW_HTML.getBytes("UTF-8")));
127                 }
128             } catch(java.io.UnsupportedEncodingException e) {}
129             return new WebResourceResponse("text/html", "UTF-8", null);
130         }
131 
getInterceptedRequests()132         List<WebResourceRequest> getInterceptedRequests() {
133             return mInterceptedRequests;
134         }
135     }
136 
137     @Before
setUp()138     public void setUp() throws Exception {
139         WebView webview = getTestEnvironment().getWebView();
140         if (webview == null) return;
141         mOnUiThread = new WebViewOnUiThread(webview);
142         mOnUiThread.getSettings().setJavaScriptEnabled(true);
143 
144         mJavascriptStatusReceiver = new JavascriptStatusReceiver();
145         mOnUiThread.addJavascriptInterface(mJavascriptStatusReceiver, JS_INTERFACE_NAME);
146         mOnUiThread.setWebViewClient(new InterceptClient(mOnUiThread));
147     }
148 
149     @After
tearDown()150     public void tearDown() throws Exception {
151         if (mOnUiThread != null) {
152             mOnUiThread.cleanUp();
153             ServiceWorkerController.getInstance().setServiceWorkerClient(null);
154         }
155     }
156 
157     @Override
createTestEnvironment()158     protected SharedWebViewTestEnvironment createTestEnvironment() {
159         Assume.assumeTrue("WebView is not available", NullWebViewUtils.isWebViewAvailable());
160 
161         SharedWebViewTestEnvironment.Builder builder = new SharedWebViewTestEnvironment.Builder();
162 
163         mActivityScenarioRule
164                 .getScenario()
165                 .onActivity(
166                         activity -> {
167                             WebView webView = ((WebViewCtsActivity) activity).getWebView();
168                             builder.setHostAppInvoker(
169                                             SharedWebViewTestEnvironment.createHostAppInvoker(
170                                                 activity))
171                                     .setContext(activity)
172                                     .setWebView(webView);
173                         });
174 
175         return builder.build();
176     }
177 
178     /**
179      * This should remain functionally equivalent to
180      * androidx.webkit.ServiceWorkerClientCompatTest#testServiceWorkerClientInterceptCallback.
181      * Modifications to this test should be reflected in that test as necessary. See
182      * http://go/modifying-webview-cts.
183      */
184     // Test correct invocation of shouldInterceptRequest for Service Workers.
185     @Test
testServiceWorkerClientInterceptCallback()186     public void testServiceWorkerClientInterceptCallback() throws Exception {
187         final InterceptServiceWorkerClient mInterceptServiceWorkerClient =
188                 new InterceptServiceWorkerClient();
189         ServiceWorkerController swController = ServiceWorkerController.getInstance();
190         swController.setServiceWorkerClient(mInterceptServiceWorkerClient);
191 
192         mOnUiThread.loadUrlAndWaitForCompletion(INDEX_URL);
193 
194         Callable<Boolean> registrationSuccess = new Callable<Boolean>() {
195             @Override
196             public Boolean call() {
197                 return mJavascriptStatusReceiver.mRegistrationSuccess;
198             }
199         };
200         PollingCheck.check("JS could not register Service Worker", POLLING_TIMEOUT,
201                 registrationSuccess);
202 
203         Callable<Boolean> receivedRequest = new Callable<Boolean>() {
204             @Override
205             public Boolean call() {
206                 return mInterceptServiceWorkerClient.getInterceptedRequests().size() >= 2;
207             }
208         };
209         PollingCheck.check("Service Worker intercept callbacks not invoked", POLLING_TIMEOUT,
210                 receivedRequest);
211 
212         List<WebResourceRequest> requests = mInterceptServiceWorkerClient.getInterceptedRequests();
213         assertEquals(2, requests.size());
214         assertEquals(SW_URL, requests.get(0).getUrl().toString());
215         assertEquals(FETCH_URL, requests.get(1).getUrl().toString());
216 
217         // Clean-up, make sure to unregister the Service Worker.
218         mOnUiThread.evaluateJavascript(SW_UNREGISTER_RAW_JS, null);
219         Callable<Boolean> unregisterSuccess = new Callable<Boolean>() {
220             @Override
221             public Boolean call() {
222                 return mJavascriptStatusReceiver.mUnregisterSuccess;
223             }
224         };
225         PollingCheck.check("JS could not unregister Service Worker", POLLING_TIMEOUT,
226                 unregisterSuccess);
227     }
228 
229     /**
230      * This should remain functionally equivalent to
231      * androidx.webkit.ServiceWorkerClientCompatTest#testSetNullServiceWorkerClient.
232      * Modifications to this test should be reflected in that test as necessary. See
233      * http://go/modifying-webview-cts.
234      */
235     // Test setting a null ServiceWorkerClient.
236     @Test
testSetNullServiceWorkerClient()237     public void testSetNullServiceWorkerClient() throws Exception {
238         ServiceWorkerController swController = ServiceWorkerController.getInstance();
239         swController.setServiceWorkerClient(null);
240         mOnUiThread.loadUrlAndWaitForCompletion(INDEX_URL);
241 
242         Callable<Boolean> registrationFailure =
243                 () -> !mJavascriptStatusReceiver.mRegistrationSuccess;
244         PollingCheck.check("JS unexpectedly registered the Service Worker", POLLING_TIMEOUT,
245                 registrationFailure);
246     }
247 
248     // Object added to the page via AddJavascriptInterface() that is used by the test Javascript to
249     // notify back to Java if the Service Worker registration was successful.
250     public final static class JavascriptStatusReceiver {
251         public volatile boolean mRegistrationSuccess = false;
252         public volatile boolean mUnregisterSuccess = false;
253 
254         @JavascriptInterface
registrationSuccess()255         public void registrationSuccess() {
256             mRegistrationSuccess = true;
257         }
258 
259         @JavascriptInterface
unregisterSuccess()260         public void unregisterSuccess() {
261             mUnregisterSuccess = true;
262         }
263     }
264 }
265 
266