- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / android / javatests / src / org / chromium / chrome / browser / test / ModalDialogTest.java
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.test;
6
7 import android.app.AlertDialog;
8 import android.content.DialogInterface;
9 import android.test.suitebuilder.annotation.MediumTest;
10 import android.util.Log;
11 import android.view.View;
12 import android.widget.Button;
13 import android.widget.CheckBox;
14 import android.widget.EditText;
15
16 import org.chromium.base.ThreadUtils;
17 import org.chromium.base.test.util.DisabledTest;
18 import org.chromium.base.test.util.Feature;
19 import org.chromium.base.test.util.UrlUtils;
20 import org.chromium.chrome.browser.JavascriptAppModalDialog;
21 import org.chromium.chrome.testshell.ChromiumTestShellTestBase;
22 import org.chromium.chrome.testshell.TabShellTabUtils;
23 import org.chromium.chrome.testshell.TabShellTabUtils.TestCallbackHelperContainerForTab;
24 import org.chromium.content.browser.test.util.Criteria;
25 import org.chromium.content.browser.test.util.CriteriaHelper;
26 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
27 import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper;
28
29 import java.util.concurrent.Callable;
30 import java.util.concurrent.ExecutionException;
31 import java.util.concurrent.TimeoutException;
32
33 /**
34  * Test suite for displaying and functioning of modal dialogs.
35  */
36 public class ModalDialogTest extends ChromiumTestShellTestBase {
37     private final static String TAG = "ModalDialogTest";
38     private final static String EMPTY_PAGE = UrlUtils.encodeHtmlDataUri(
39             "<html><title>Modal Dialog Test</title><p>Testcase.</p></title></html>");
40     private final static String BEFORE_UNLOAD_URL = UrlUtils.encodeHtmlDataUri(
41             "<html>" +
42             "<head><script>window.onbeforeunload=function() {" +
43             "return 'Are you sure?';" +
44             "};</script></head></html>");
45
46     @Override
47     public void setUp() throws Exception {
48         super.setUp();
49         launchChromiumTestShellWithUrl(EMPTY_PAGE);
50         assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
51     }
52
53     /**
54      * Verifies modal alert-dialog appearance and that JavaScript execution is
55      * able to continue after dismissal.
56      */
57     @MediumTest
58     @Feature({"Browser", "Main"})
59     public void testAlertModalDialog()
60             throws InterruptedException, TimeoutException, ExecutionException {
61         final OnEvaluateJavaScriptResultHelper scriptEvent =
62                 executeJavaScriptAndWaitForDialog("alert('Hello Android!');");
63
64         JavascriptAppModalDialog jsDialog = getCurrentDialog();
65         assertNotNull("No dialog showing.", jsDialog);
66
67         clickOk(jsDialog);
68         assertTrue("JavaScript execution should continue after closing prompt.",
69                 scriptEvent.waitUntilHasValue());
70     }
71
72     /**
73      * Verifies that clicking on a button twice doesn't crash.
74      */
75     @MediumTest
76     @Feature({"Browser", "Main"})
77     public void testAlertModalDialogWithTwoClicks()
78             throws InterruptedException, TimeoutException, ExecutionException {
79         OnEvaluateJavaScriptResultHelper scriptEvent =
80                 executeJavaScriptAndWaitForDialog("alert('Hello Android');");
81         JavascriptAppModalDialog jsDialog = getCurrentDialog();
82         assertNotNull("No dialog showing.", jsDialog);
83
84         clickOk(jsDialog);
85         clickOk(jsDialog);
86
87         assertTrue("JavaScript execution should continue after closing prompt.",
88                 scriptEvent.waitUntilHasValue());
89     }
90
91     /**
92      * Verifies that modal confirm-dialogs display, two buttons are visible and
93      * the return value of [Ok] equals true, [Cancel] equals false.
94      */
95     @MediumTest
96     @Feature({"Browser", "Main"})
97     public void testConfirmModalDialog()
98             throws InterruptedException, TimeoutException, ExecutionException {
99         OnEvaluateJavaScriptResultHelper scriptEvent =
100                 executeJavaScriptAndWaitForDialog("confirm('Android');");
101
102         JavascriptAppModalDialog jsDialog = getCurrentDialog();
103         assertNotNull("No dialog showing.", jsDialog);
104
105         Button[] buttons = getAlertDialogButtons(jsDialog.getDialogForTest());
106         assertNotNull("No cancel button in confirm dialog.", buttons[0]);
107         assertEquals("Cancel button is not visible.", View.VISIBLE, buttons[0].getVisibility());
108         if (buttons[1] != null) {
109             assertNotSame("Neutral button visible when it should not.",
110                     View.VISIBLE, buttons[1].getVisibility());
111         }
112         assertNotNull("No OK button in confirm dialog.", buttons[2]);
113         assertEquals("OK button is not visible.", View.VISIBLE, buttons[2].getVisibility());
114
115         clickOk(jsDialog);
116         assertTrue("JavaScript execution should continue after closing dialog.",
117                 scriptEvent.waitUntilHasValue());
118
119         String resultString = scriptEvent.getJsonResultAndClear();
120         assertEquals("Invalid return value.", "true", resultString);
121
122         // Try again, pressing cancel this time.
123         scriptEvent = executeJavaScriptAndWaitForDialog("confirm('Android');");
124         jsDialog = getCurrentDialog();
125         assertNotNull("No dialog showing.", jsDialog);
126
127         clickCancel(jsDialog);
128         assertTrue("JavaScript execution should continue after closing dialog.",
129                 scriptEvent.waitUntilHasValue());
130
131         resultString = scriptEvent.getJsonResultAndClear();
132         assertEquals("Invalid return value.", "false", resultString);
133     }
134
135     /**
136      * Verifies that modal prompt-dialogs display and the result is returned.
137      */
138     @MediumTest
139     @Feature({"Browser", "Main"})
140     public void testPromptModalDialog()
141             throws InterruptedException, TimeoutException, ExecutionException {
142         final String promptText = "Hello Android!";
143         final OnEvaluateJavaScriptResultHelper scriptEvent =
144                 executeJavaScriptAndWaitForDialog("prompt('Android', 'default');");
145
146         final JavascriptAppModalDialog jsDialog = getCurrentDialog();
147         assertNotNull("No dialog showing.", jsDialog);
148
149         // Set the text in the prompt field of the dialog.
150         boolean result = ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
151             @Override
152             public Boolean call() {
153                 EditText prompt = (EditText) jsDialog.getDialogForTest().findViewById(
154                         org.chromium.chrome.R.id.js_modal_dialog_prompt);
155                 if (prompt == null) return false;
156                 prompt.setText(promptText);
157                 return true;
158             }
159         });
160         assertTrue("Failed to find prompt view in prompt dialog.", result);
161
162         clickOk(jsDialog);
163         assertTrue("JavaScript execution should continue after closing prompt.",
164                 scriptEvent.waitUntilHasValue());
165
166         String resultString = scriptEvent.getJsonResultAndClear();
167         assertEquals("Invalid return value.", '"' + promptText + '"', resultString);
168     }
169
170     /**
171      * Verifies beforeunload dialogs are shown and they block/allow navigation
172      * as appropriate.
173      */
174     //@MediumTest
175     //@Feature({"Browser", "Main"})
176     @DisabledTest //crbug/270593
177     public void testBeforeUnloadDialog()
178             throws InterruptedException, TimeoutException, ExecutionException {
179         loadUrlWithSanitization(BEFORE_UNLOAD_URL);
180         executeJavaScriptAndWaitForDialog("history.back();");
181
182         JavascriptAppModalDialog jsDialog = getCurrentDialog();
183         assertNotNull("No dialog showing.", jsDialog);
184         checkButtonPresenceVisibilityText(
185                 jsDialog, 0, org.chromium.chrome.R.string.stay_on_this_page,
186                 "Stay on this page");
187         clickCancel(jsDialog);
188
189         assertEquals(BEFORE_UNLOAD_URL, getActivity().getActiveContentView().getUrl());
190         executeJavaScriptAndWaitForDialog("history.back();");
191
192         jsDialog = getCurrentDialog();
193         assertNotNull("No dialog showing.", jsDialog);
194         checkButtonPresenceVisibilityText(
195                 jsDialog, 2, org.chromium.chrome.R.string.leave_this_page,
196                 "Leave this page");
197
198         final TestCallbackHelperContainer.OnPageFinishedHelper onPageLoaded =
199                 getActiveTabTestCallbackHelperContainer().getOnPageFinishedHelper();
200         int callCount = onPageLoaded.getCallCount();
201         clickOk(jsDialog);
202         onPageLoaded.waitForCallback(callCount);
203         assertEquals(EMPTY_PAGE, getActivity().getActiveContentView().getUrl());
204     }
205
206     /**
207      * Verifies that when showing a beforeunload dialogs as a result of a page
208      * reload, the correct UI strings are used.
209      */
210     @MediumTest
211     @Feature({"Browser", "Main"})
212     public void testBeforeUnloadOnReloadDialog()
213             throws InterruptedException, TimeoutException, ExecutionException {
214         loadUrlWithSanitization(BEFORE_UNLOAD_URL);
215         executeJavaScriptAndWaitForDialog("window.location.reload();");
216
217         JavascriptAppModalDialog jsDialog = getCurrentDialog();
218         assertNotNull("No dialog showing.", jsDialog);
219
220         checkButtonPresenceVisibilityText(
221                 jsDialog, 0, org.chromium.chrome.R.string.dont_reload_this_page,
222                 "Don't reload this page");
223         checkButtonPresenceVisibilityText(
224                 jsDialog, 2, org.chromium.chrome.R.string.reload_this_page,
225                 "Reload this page");
226     }
227
228     /**
229      * Verifies that repeated dialogs give the option to disable dialogs
230      * altogether and then that disabling them works.
231      */
232     @MediumTest
233     @Feature({"Browser", "Main"})
234     public void testDisableRepeatedDialogs()
235             throws InterruptedException, TimeoutException, ExecutionException {
236         OnEvaluateJavaScriptResultHelper scriptEvent =
237                 executeJavaScriptAndWaitForDialog("alert('Android');");
238
239         // Show a dialog once.
240         JavascriptAppModalDialog jsDialog = getCurrentDialog();
241         assertNotNull("No dialog showing.", jsDialog);
242
243         clickCancel(jsDialog);
244         scriptEvent.waitUntilHasValue();
245
246         // Show it again, it should have the option to suppress subsequent dialogs.
247         scriptEvent = executeJavaScriptAndWaitForDialog("alert('Android');");
248         jsDialog = getCurrentDialog();
249         assertNotNull("No dialog showing.", jsDialog);
250         final AlertDialog dialog = jsDialog.getDialogForTest();
251         String errorMessage = ThreadUtils.runOnUiThreadBlocking(new Callable<String>() {
252             @Override
253             public String call() {
254                 final CheckBox suppress = (CheckBox) dialog.findViewById(
255                         org.chromium.chrome.R.id.suppress_js_modal_dialogs);
256                 if (suppress == null) return "Suppress checkbox not found.";
257                 if (suppress.getVisibility() != View.VISIBLE) {
258                     return "Suppress checkbox is not visible.";
259                 }
260                 suppress.setChecked(true);
261                 return null;
262             }
263         });
264         assertNull(errorMessage, errorMessage);
265         clickCancel(jsDialog);
266         scriptEvent.waitUntilHasValue();
267
268         scriptEvent.evaluateJavaScript(getActivity().getActiveContentView().getContentViewCore(),
269                 "alert('Android');");
270         assertTrue("No further dialog boxes should be shown.", scriptEvent.waitUntilHasValue());
271     }
272
273     /**
274      * Displays a dialog and closes the tab in the background before attempting
275      * to accept the dialog. Verifies that the dialog is dismissed when the tab
276      * is closed.
277      */
278     @MediumTest
279     @Feature({"Browser", "Main"})
280     public void testDialogDismissedAfterClosingTab()
281             throws InterruptedException, TimeoutException, ExecutionException {
282         executeJavaScriptAndWaitForDialog("alert('Android')");
283
284         final TestCallbackHelperContainerForTab.OnCloseTabHelper onTabClosed =
285                 getActiveTabTestCallbackHelperContainer().getOnCloseTabHelper();
286         int callCount = onTabClosed.getCallCount();
287         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
288             @Override
289             public void run() {
290                 getActivity().createTab(EMPTY_PAGE);
291             }
292         });
293         onTabClosed.waitForCallback(callCount);
294
295         // Closing the tab should have dismissed the dialog.
296         boolean criteriaSatisfied = CriteriaHelper.pollForCriteria(
297                 new JavascriptAppModalDialogShownCriteria(false));
298         assertTrue("The dialog should have been dismissed when its tab was closed.",
299                 criteriaSatisfied);
300     }
301
302     /**
303      * Asynchronously executes the given code for spawning a dialog and waits
304      * for the dialog to be visible.
305      */
306     private OnEvaluateJavaScriptResultHelper executeJavaScriptAndWaitForDialog(String script)
307             throws InterruptedException {
308         final OnEvaluateJavaScriptResultHelper helper =
309                 getActiveTabTestCallbackHelperContainer().getOnEvaluateJavaScriptResultHelper();
310         return executeJavaScriptAndWaitForDialog(helper, script);
311     }
312
313     /**
314      * Given a JavaScript evaluation helper, asynchronously executes the given
315      * code for spawning a dialog and waits for the dialog to be visible.
316      */
317     private OnEvaluateJavaScriptResultHelper executeJavaScriptAndWaitForDialog(
318             final OnEvaluateJavaScriptResultHelper helper, String script)
319             throws InterruptedException {
320         helper.evaluateJavaScript(getActivity().getActiveContentView().getContentViewCore(),
321                 script);
322         boolean criteriaSatisfied = CriteriaHelper.pollForCriteria(
323                 new JavascriptAppModalDialogShownCriteria(true));
324         assertTrue("Could not spawn or locate a modal dialog.", criteriaSatisfied);
325         return helper;
326     }
327
328     /**
329      * Returns an array of the 3 buttons for this dialog, in the order
330      * BUTTON_NEGATIVE, BUTTON_NEUTRAL and BUTTON_POSITIVE. Any of these values
331      * can be null.
332      */
333     private Button[] getAlertDialogButtons(final AlertDialog dialog) throws ExecutionException {
334         return ThreadUtils.runOnUiThreadBlocking(new Callable<Button[]>() {
335             @Override
336             public Button[] call() {
337                 final Button[] buttons = new Button[3];
338                 buttons[0] = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
339                 buttons[1] = dialog.getButton(DialogInterface.BUTTON_NEUTRAL);
340                 buttons[2] = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
341                 return buttons;
342             }
343         });
344     }
345
346     /**
347      * Returns the current JavaScript modal dialog showing or null if no such dialog is currently
348      * showing.
349      */
350     private JavascriptAppModalDialog getCurrentDialog() throws ExecutionException {
351         return ThreadUtils.runOnUiThreadBlocking(new Callable<JavascriptAppModalDialog>() {
352             @Override
353             public JavascriptAppModalDialog call() {
354                 return JavascriptAppModalDialog.getCurrentDialogForTest();
355             }
356         });
357     }
358
359     private static class JavascriptAppModalDialogShownCriteria implements Criteria {
360         private final boolean mShouldBeShown;
361
362         public JavascriptAppModalDialogShownCriteria(boolean shouldBeShown) {
363             mShouldBeShown = shouldBeShown;
364         }
365
366         @Override
367         public boolean isSatisfied() {
368             try {
369                 return ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
370                     @Override
371                     public Boolean call() {
372                         final boolean isShown =
373                                 JavascriptAppModalDialog.getCurrentDialogForTest() != null;
374                         return mShouldBeShown == isShown;
375                     }
376                 });
377             } catch (ExecutionException e) {
378                 Log.e(TAG, "Failed to getCurrentDialog", e);
379                 return false;
380             }
381         }
382     }
383
384     /**
385      * Simulates pressing the OK button of the passed dialog.
386      */
387     private void clickOk(JavascriptAppModalDialog dialog) {
388         clickButton(dialog, DialogInterface.BUTTON_POSITIVE);
389     }
390
391     /**
392      * Simulates pressing the Cancel button of the passed dialog.
393      */
394     private void clickCancel(JavascriptAppModalDialog dialog) {
395         clickButton(dialog, DialogInterface.BUTTON_NEGATIVE);
396     }
397
398     private void clickButton(final JavascriptAppModalDialog dialog, final int whichButton) {
399         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
400             @Override
401             public void run() {
402                 dialog.onClick(null, whichButton);
403             }
404         });
405     }
406
407     private void checkButtonPresenceVisibilityText(
408             JavascriptAppModalDialog jsDialog, int buttonIndex,
409             int expectedTextResourceId, String readableName) throws ExecutionException {
410         final Button[] buttons = getAlertDialogButtons(jsDialog.getDialogForTest());
411         final Button button = buttons[buttonIndex];
412         assertNotNull("No '" + readableName + "' button in confirm dialog.", button);
413         assertEquals("'" + readableName + "' button is not visible.",
414                 View.VISIBLE,
415                 button.getVisibility());
416         assertEquals("'" + readableName + "' button has wrong text",
417                 getActivity().getResources().getString(expectedTextResourceId),
418                 button.getText().toString());
419     }
420
421     private TestCallbackHelperContainerForTab getActiveTabTestCallbackHelperContainer() {
422         return TabShellTabUtils.getTestCallbackHelperContainer(getActivity().getActiveTab());
423     }
424 }