• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertTrue;
24 
25 import android.graphics.Bitmap;
26 import android.os.Message;
27 import android.os.SystemClock;
28 import android.platform.test.annotations.AppModeFull;
29 import android.util.Base64;
30 import android.view.MotionEvent;
31 import android.view.ViewGroup;
32 import android.view.ViewParent;
33 import android.webkit.ConsoleMessage;
34 import android.webkit.JsPromptResult;
35 import android.webkit.JsResult;
36 import android.webkit.WebIconDatabase;
37 import android.webkit.WebSettings;
38 import android.webkit.WebView;
39 import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
40 
41 import androidx.test.ext.junit.rules.ActivityScenarioRule;
42 import androidx.test.ext.junit.runners.AndroidJUnit4;
43 import androidx.test.filters.MediumTest;
44 
45 import com.android.compatibility.common.util.NullWebViewUtils;
46 import com.android.compatibility.common.util.PollingCheck;
47 
48 import com.google.common.util.concurrent.SettableFuture;
49 
50 import org.junit.After;
51 import org.junit.Assume;
52 import org.junit.Before;
53 import org.junit.Rule;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 
57 import java.util.concurrent.ArrayBlockingQueue;
58 import java.util.concurrent.BlockingQueue;
59 
60 @AppModeFull
61 @MediumTest
62 @RunWith(AndroidJUnit4.class)
63 public class WebChromeClientTest extends SharedWebViewTest{
64     private static final String JAVASCRIPT_UNLOAD = "javascript unload";
65     private static final String LISTENER_ADDED = "listener added";
66     private static final String TOUCH_RECEIVED = "touch received";
67 
68     @Rule
69     public ActivityScenarioRule mActivityScenarioRule =
70             new ActivityScenarioRule(WebViewCtsActivity.class);
71 
72     private SharedSdkWebServer mWebServer;
73     private WebIconDatabase mIconDb;
74     private WebViewOnUiThread mOnUiThread;
75     private boolean mBlockWindowCreationSync;
76     private boolean mBlockWindowCreationAsync;
77 
78     @Before
setUp()79     public void setUp() throws Exception {
80         WebView webview = getTestEnvironment().getWebView();
81         if (webview != null) {
82             mOnUiThread = new WebViewOnUiThread(webview);
83         }
84         mWebServer = getTestEnvironment().getSetupWebServer(SslMode.INSECURE);
85     }
86 
87     @After
tearDown()88     public void tearDown() throws Exception {
89         if (mOnUiThread != null) {
90             mOnUiThread.cleanUp();
91         }
92         if (mWebServer != null) {
93             mWebServer.shutdown();
94         }
95         if (mIconDb != null) {
96             mIconDb.removeAllIcons();
97             mIconDb.close();
98         }
99     }
100 
101     @Override
createTestEnvironment()102     protected SharedWebViewTestEnvironment createTestEnvironment() {
103         Assume.assumeTrue("WebView is not available", NullWebViewUtils.isWebViewAvailable());
104 
105         SharedWebViewTestEnvironment.Builder builder = new SharedWebViewTestEnvironment.Builder();
106 
107         mActivityScenarioRule
108                 .getScenario()
109                 .onActivity(
110                         activity -> {
111                             WebView webView = ((WebViewCtsActivity) activity).getWebView();
112                             builder.setHostAppInvoker(
113                                             SharedWebViewTestEnvironment.createHostAppInvoker(
114                                                     activity))
115                                     .setContext(activity)
116                                     .setWebView(webView)
117                                     .setRootLayout(((WebViewCtsActivity) activity).getRootLayout());
118                         });
119 
120         SharedWebViewTestEnvironment environment = builder.build();
121         return environment;
122     }
123 
124 
125     @Test
testOnProgressChanged()126     public void testOnProgressChanged() {
127         final MockWebChromeClient webChromeClient = new MockWebChromeClient();
128         mOnUiThread.setWebChromeClient(webChromeClient);
129 
130         assertFalse(webChromeClient.hadOnProgressChanged());
131         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
132         mOnUiThread.loadUrlAndWaitForCompletion(url);
133 
134         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
135             @Override
136             protected boolean check() {
137                 return webChromeClient.hadOnProgressChanged();
138             }
139         }.run();
140     }
141 
142     @Test
testOnReceivedTitle()143     public void testOnReceivedTitle() throws Exception {
144         final MockWebChromeClient webChromeClient = new MockWebChromeClient();
145         mOnUiThread.setWebChromeClient(webChromeClient);
146 
147         assertFalse(webChromeClient.hadOnReceivedTitle());
148         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
149         mOnUiThread.loadUrlAndWaitForCompletion(url);
150 
151         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
152             @Override
153             protected boolean check() {
154                 return webChromeClient.hadOnReceivedTitle();
155             }
156         }.run();
157         assertTrue(webChromeClient.hadOnReceivedTitle());
158         assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, webChromeClient.getPageTitle());
159     }
160 
161     @Test
testOnReceivedIcon()162     public void testOnReceivedIcon() throws Throwable {
163         final MockWebChromeClient webChromeClient = new MockWebChromeClient();
164         mOnUiThread.setWebChromeClient(webChromeClient);
165 
166         WebkitUtils.onMainThreadSync(() -> {
167             // getInstance must run on the UI thread
168             mIconDb = WebIconDatabase.getInstance();
169             String dbPath = getTestEnvironment().getContext().getFilesDir().toString() + "/icons";
170             mIconDb.open(dbPath);
171         });
172         getTestEnvironment().waitForIdleSync();
173         Thread.sleep(100); // Wait for open to be received on the icon db thread.
174 
175         assertFalse(webChromeClient.hadOnReceivedIcon());
176         assertNull(mOnUiThread.getFavicon());
177 
178         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
179         mOnUiThread.loadUrlAndWaitForCompletion(url);
180 
181         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
182             @Override
183             protected boolean check() {
184                 return webChromeClient.hadOnReceivedIcon();
185             }
186         }.run();
187         assertNotNull(mOnUiThread.getFavicon());
188     }
189 
runWindowTest(boolean expectWindowClose)190     public void runWindowTest(boolean expectWindowClose) throws Exception {
191         final MockWebChromeClient webChromeClient = new MockWebChromeClient();
192         mOnUiThread.setWebChromeClient(webChromeClient);
193 
194         final WebSettings settings = mOnUiThread.getSettings();
195         settings.setJavaScriptEnabled(true);
196         settings.setJavaScriptCanOpenWindowsAutomatically(true);
197         settings.setSupportMultipleWindows(true);
198 
199         assertFalse(webChromeClient.hadOnCreateWindow());
200 
201         // Load a page that opens a child window and sets a timeout after which the child
202         // will be closed.
203         mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.
204                 getAssetUrl(TestHtmlConstants.JS_WINDOW_URL));
205 
206         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
207             @Override
208             protected boolean check() {
209                 return webChromeClient.hadOnCreateWindow();
210             }
211         }.run();
212 
213         if (expectWindowClose) {
214             new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
215                 @Override
216                 protected boolean check() {
217                     return webChromeClient.hadOnCloseWindow();
218                 }
219             }.run();
220         } else {
221             assertFalse(webChromeClient.hadOnCloseWindow());
222         }
223     }
224     @Test
testWindows()225     public void testWindows() throws Exception {
226         runWindowTest(true);
227     }
228 
229     @Test
testBlockWindowsSync()230     public void testBlockWindowsSync() throws Exception {
231         mBlockWindowCreationSync = true;
232         runWindowTest(false);
233     }
234 
235     @Test
testBlockWindowsAsync()236     public void testBlockWindowsAsync() throws Exception {
237         mBlockWindowCreationAsync = true;
238         runWindowTest(false);
239     }
240 
241     // Note that test is still a little flaky. See b/119468441.
242     @Test
testOnJsBeforeUnloadIsCalled()243     public void testOnJsBeforeUnloadIsCalled() throws Exception {
244         final WebSettings settings = mOnUiThread.getSettings();
245         settings.setJavaScriptEnabled(true);
246         settings.setJavaScriptCanOpenWindowsAutomatically(true);
247 
248         final BlockingQueue<String> pageTitleQueue = new ArrayBlockingQueue<>(3);
249         final SettableFuture<Void> onJsBeforeUnloadFuture = SettableFuture.create();
250         final MockWebChromeClient webChromeClientWaitTitle = new MockWebChromeClient() {
251             @Override
252             public void onReceivedTitle(WebView view, String title) {
253                 super.onReceivedTitle(view, title);
254                 pageTitleQueue.add(title);
255             }
256 
257             @Override
258             public boolean onJsBeforeUnload(
259                 WebView view, String url, String message, JsResult result) {
260                 boolean ret = super.onJsBeforeUnload(view, url, message, result);
261                 onJsBeforeUnloadFuture.set(null);
262                 return ret;
263             }
264         };
265         mOnUiThread.setWebChromeClient(webChromeClientWaitTitle);
266 
267         mOnUiThread.loadUrlAndWaitForCompletion(
268             mWebServer.getAssetUrl(TestHtmlConstants.JS_UNLOAD_URL));
269 
270         assertEquals(JAVASCRIPT_UNLOAD, WebkitUtils.waitForNextQueueElement(pageTitleQueue));
271         assertEquals(LISTENER_ADDED, WebkitUtils.waitForNextQueueElement(pageTitleQueue));
272         // Send a user gesture, required for unload to execute since WebView version 60.
273         tapWebView();
274         assertEquals(TOUCH_RECEIVED, WebkitUtils.waitForNextQueueElement(pageTitleQueue));
275 
276         // unload should trigger when we try to navigate away
277         mOnUiThread.loadUrlAndWaitForCompletion(
278             mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
279 
280         WebkitUtils.waitForFuture(onJsBeforeUnloadFuture);
281     }
282 
283     @Test
testOnJsAlert()284     public void testOnJsAlert() throws Exception {
285         final MockWebChromeClient webChromeClient = new MockWebChromeClient();
286         mOnUiThread.setWebChromeClient(webChromeClient);
287 
288         final WebSettings settings = mOnUiThread.getSettings();
289         settings.setJavaScriptEnabled(true);
290         settings.setJavaScriptCanOpenWindowsAutomatically(true);
291 
292         assertFalse(webChromeClient.hadOnJsAlert());
293 
294         String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_ALERT_URL);
295         mOnUiThread.loadUrlAndWaitForCompletion(url);
296 
297         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
298             @Override
299             protected boolean check() {
300                 return webChromeClient.hadOnJsAlert();
301             }
302         }.run();
303         assertEquals(webChromeClient.getMessage(), "testOnJsAlert");
304     }
305 
306     @Test
testOnJsConfirm()307     public void testOnJsConfirm() throws Exception {
308         final MockWebChromeClient webChromeClient = new MockWebChromeClient();
309         mOnUiThread.setWebChromeClient(webChromeClient);
310 
311         final WebSettings settings = mOnUiThread.getSettings();
312         settings.setJavaScriptEnabled(true);
313         settings.setJavaScriptCanOpenWindowsAutomatically(true);
314 
315         assertFalse(webChromeClient.hadOnJsConfirm());
316 
317         String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_CONFIRM_URL);
318         mOnUiThread.loadUrlAndWaitForCompletion(url);
319 
320         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
321             @Override
322             protected boolean check() {
323                 return webChromeClient.hadOnJsConfirm();
324             }
325         }.run();
326         assertEquals(webChromeClient.getMessage(), "testOnJsConfirm");
327     }
328 
329     @Test
testOnJsPrompt()330     public void testOnJsPrompt() throws Exception {
331         final MockWebChromeClient webChromeClient = new MockWebChromeClient();
332         mOnUiThread.setWebChromeClient(webChromeClient);
333 
334         final WebSettings settings = mOnUiThread.getSettings();
335         settings.setJavaScriptEnabled(true);
336         settings.setJavaScriptCanOpenWindowsAutomatically(true);
337 
338         assertFalse(webChromeClient.hadOnJsPrompt());
339 
340         final String promptResult = "CTS";
341         webChromeClient.setPromptResult(promptResult);
342         String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_PROMPT_URL);
343         mOnUiThread.loadUrlAndWaitForCompletion(url);
344 
345         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
346             @Override
347             protected boolean check() {
348                 return webChromeClient.hadOnJsPrompt();
349             }
350         }.run();
351         // the result returned by the client gets set as the page title
352         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
353             @Override
354             protected boolean check() {
355                 return mOnUiThread.getTitle().equals(promptResult);
356             }
357         }.run();
358         assertEquals(webChromeClient.getMessage(), "testOnJsPrompt");
359     }
360 
361     @Test
testOnConsoleMessage()362     public void testOnConsoleMessage() throws Exception {
363         int numConsoleMessages = 4;
364         final BlockingQueue<ConsoleMessage> consoleMessageQueue =
365                 new ArrayBlockingQueue<>(numConsoleMessages);
366         final MockWebChromeClient webChromeClient = new MockWebChromeClient() {
367             @Override
368             public boolean onConsoleMessage(ConsoleMessage message) {
369                 consoleMessageQueue.add(message);
370                 // return false for default handling; i.e. printing the message.
371                 return false;
372             }
373         };
374         mOnUiThread.setWebChromeClient(webChromeClient);
375 
376         mOnUiThread.getSettings().setJavaScriptEnabled(true);
377         // Note: we assert line numbers, which are relative to the line in the HTML file. So, "\n"
378         // is significant in this test, and make sure to update consoleLineNumberOffset when
379         // editing the HTML.
380         final int consoleLineNumberOffset = 3;
381         final String unencodedHtml = "<html>\n"
382                 + "<script>\n"
383                 + "  console.log('message0');\n"
384                 + "  console.warn('message1');\n"
385                 + "  console.error('message2');\n"
386                 + "  console.info('message3');\n"
387                 + "</script>\n"
388                 + "</html>\n";
389         final String mimeType = null;
390         final String encoding = "base64";
391         String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING);
392         mOnUiThread.loadDataAndWaitForCompletion(encodedHtml, mimeType, encoding);
393 
394         // Expected message levels correspond to the order of the console messages defined above.
395         ConsoleMessage.MessageLevel[] expectedMessageLevels = {
396             ConsoleMessage.MessageLevel.LOG,
397             ConsoleMessage.MessageLevel.WARNING,
398             ConsoleMessage.MessageLevel.ERROR,
399             ConsoleMessage.MessageLevel.LOG,
400         };
401         for (int k = 0; k < numConsoleMessages; k++) {
402             final ConsoleMessage consoleMessage =
403                     WebkitUtils.waitForNextQueueElement(consoleMessageQueue);
404             final ConsoleMessage.MessageLevel expectedMessageLevel = expectedMessageLevels[k];
405             assertEquals("message " + k + " had wrong level",
406                     expectedMessageLevel,
407                     consoleMessage.messageLevel());
408             final String expectedMessage = "message" + k;
409             assertEquals("message " + k + " had wrong message",
410                     expectedMessage,
411                     consoleMessage.message());
412             final int expectedLineNumber = k + consoleLineNumberOffset;
413             assertEquals("message " + k + " had wrong line number",
414                     expectedLineNumber,
415                     consoleMessage.lineNumber());
416         }
417     }
418 
419     /**
420      * Taps in the the center of a webview.
421      */
tapWebView()422     private void tapWebView() {
423         int[] location = mOnUiThread.getLocationOnScreen();
424         int middleX = location[0] + mOnUiThread.getWebView().getWidth() / 2;
425         int middleY = location[1] + mOnUiThread.getWebView().getHeight() / 2;
426 
427         long timeDown = SystemClock.uptimeMillis();
428         getTestEnvironment().sendPointerSync(
429                 MotionEvent.obtain(timeDown, timeDown, MotionEvent.ACTION_DOWN,
430                         middleX, middleY, 0));
431 
432         long timeUp = SystemClock.uptimeMillis();
433         getTestEnvironment().sendPointerSync(
434                 MotionEvent.obtain(timeUp, timeUp, MotionEvent.ACTION_UP,
435                         middleX, middleY, 0));
436 
437         // Wait for the system to process all events in the queue
438         getTestEnvironment().waitForIdleSync();
439     }
440 
441     private class MockWebChromeClient extends WaitForProgressClient {
442         private boolean mHadOnProgressChanged;
443         private boolean mHadOnReceivedTitle;
444         private String mPageTitle;
445         private boolean mHadOnJsAlert;
446         private boolean mHadOnJsConfirm;
447         private boolean mHadOnJsPrompt;
448         private boolean mHadOnJsBeforeUnload;
449         private String mMessage;
450         private String mPromptResult;
451         private boolean mHadOnCloseWindow;
452         private boolean mHadOnCreateWindow;
453         private boolean mHadOnRequestFocus;
454         private boolean mHadOnReceivedIcon;
455         private WebView mChildWebView;
456 
MockWebChromeClient()457         public MockWebChromeClient() {
458             super(mOnUiThread);
459         }
460 
setPromptResult(String promptResult)461         public void setPromptResult(String promptResult) {
462             mPromptResult = promptResult;
463         }
464 
hadOnProgressChanged()465         public boolean hadOnProgressChanged() {
466             return mHadOnProgressChanged;
467         }
468 
hadOnReceivedTitle()469         public boolean hadOnReceivedTitle() {
470             return mHadOnReceivedTitle;
471         }
472 
getPageTitle()473         public String getPageTitle() {
474             return mPageTitle;
475         }
476 
hadOnJsAlert()477         public boolean hadOnJsAlert() {
478             return mHadOnJsAlert;
479         }
480 
hadOnJsConfirm()481         public boolean hadOnJsConfirm() {
482             return mHadOnJsConfirm;
483         }
484 
hadOnJsPrompt()485         public boolean hadOnJsPrompt() {
486             return mHadOnJsPrompt;
487         }
488 
hadOnJsBeforeUnload()489         public boolean hadOnJsBeforeUnload() {
490             return mHadOnJsBeforeUnload;
491         }
492 
hadOnCreateWindow()493         public boolean hadOnCreateWindow() {
494             return mHadOnCreateWindow;
495         }
496 
hadOnCloseWindow()497         public boolean hadOnCloseWindow() {
498             return mHadOnCloseWindow;
499         }
500 
hadOnRequestFocus()501         public boolean hadOnRequestFocus() {
502             return mHadOnRequestFocus;
503         }
504 
hadOnReceivedIcon()505         public boolean hadOnReceivedIcon() {
506             return mHadOnReceivedIcon;
507         }
508 
getMessage()509         public String getMessage() {
510             return mMessage;
511         }
512 
513         @Override
onProgressChanged(WebView view, int newProgress)514         public void onProgressChanged(WebView view, int newProgress) {
515             super.onProgressChanged(view, newProgress);
516             mHadOnProgressChanged = true;
517         }
518 
519         @Override
onReceivedTitle(WebView view, String title)520         public void onReceivedTitle(WebView view, String title) {
521             super.onReceivedTitle(view, title);
522             mPageTitle = title;
523             mHadOnReceivedTitle = true;
524         }
525 
526         @Override
onJsAlert(WebView view, String url, String message, JsResult result)527         public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
528             super.onJsAlert(view, url, message, result);
529             mHadOnJsAlert = true;
530             mMessage = message;
531             result.confirm();
532             return true;
533         }
534 
535         @Override
onJsConfirm(WebView view, String url, String message, JsResult result)536         public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
537             super.onJsConfirm(view, url, message, result);
538             mHadOnJsConfirm = true;
539             mMessage = message;
540             result.confirm();
541             return true;
542         }
543 
544         @Override
onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)545         public boolean onJsPrompt(WebView view, String url, String message,
546                 String defaultValue, JsPromptResult result) {
547             super.onJsPrompt(view, url, message, defaultValue, result);
548             mHadOnJsPrompt = true;
549             mMessage = message;
550             result.confirm(mPromptResult);
551             return true;
552         }
553 
554         @Override
onJsBeforeUnload(WebView view, String url, String message, JsResult result)555         public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
556             super.onJsBeforeUnload(view, url, message, result);
557             mHadOnJsBeforeUnload = true;
558             mMessage = message;
559             result.confirm();
560             return true;
561         }
562 
563         @Override
onCloseWindow(WebView window)564         public void onCloseWindow(WebView window) {
565             super.onCloseWindow(window);
566             mHadOnCloseWindow = true;
567 
568             if (mChildWebView != null) {
569                 ViewParent parent =  mChildWebView.getParent();
570                 if (parent instanceof ViewGroup) {
571                     ((ViewGroup) parent).removeView(mChildWebView);
572                 }
573                 mChildWebView.destroy();
574             }
575 
576         }
577 
578         @Override
onCreateWindow(WebView view, boolean dialog, boolean userGesture, Message resultMsg)579         public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture,
580                 Message resultMsg) {
581             mHadOnCreateWindow = true;
582             if (mBlockWindowCreationSync) {
583                 return false;
584             }
585             WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
586             if (mBlockWindowCreationAsync) {
587                 transport.setWebView(null);
588             } else {
589                 mChildWebView = new WebView(getTestEnvironment().getContext());
590                 final WebSettings settings = mChildWebView.getSettings();
591                 settings.setJavaScriptEnabled(true);
592                 mChildWebView.setWebChromeClient(this);
593                 transport.setWebView(mChildWebView);
594                 getTestEnvironment().addContentView(mChildWebView, new ViewGroup.LayoutParams(
595                         ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
596             }
597             resultMsg.sendToTarget();
598             return true;
599         }
600 
601         @Override
onRequestFocus(WebView view)602         public void onRequestFocus(WebView view) {
603             mHadOnRequestFocus = true;
604         }
605 
606         @Override
onReceivedIcon(WebView view, Bitmap icon)607         public void onReceivedIcon(WebView view, Bitmap icon) {
608             mHadOnReceivedIcon = true;
609         }
610     }
611 }
612