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