blob: 3d103e1e6e40a47c3b9f4ddb71326f61cc545e7a [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/javascript_dialogs/javascript_dialog_tab_helper.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/ukm/content/source_url_recorder.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source.h"
using DismissalCause = JavaScriptDialogTabHelper::DismissalCause;
class JavaScriptDialogTest : public InProcessBrowserTest {
private:
friend class JavaScriptDialogDismissalCauseTester;
};
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest, ReloadDoesntHang) {
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
JavaScriptDialogTabHelper* js_helper =
JavaScriptDialogTabHelper::FromWebContents(tab);
// Show a dialog.
scoped_refptr<content::MessageLoopRunner> runner =
new content::MessageLoopRunner;
js_helper->SetDialogShownCallbackForTesting(runner->QuitClosure());
tab->GetMainFrame()->ExecuteJavaScriptForTests(base::UTF8ToUTF16("alert()"));
runner->Run();
// Try reloading.
tab->GetController().Reload(content::ReloadType::NORMAL, false);
content::WaitForLoadStop(tab);
// If the WaitForLoadStop doesn't hang forever, we've passed.
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest,
ClosingPageSharingRendererDoesntHang) {
// Turn off popup blocking.
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch(
switches::kDisablePopupBlocking);
// Two tabs, one render process.
content::WebContents* tab1 =
browser()->tab_strip_model()->GetActiveWebContents();
content::WebContentsAddedObserver new_wc_observer;
tab1->GetMainFrame()->ExecuteJavaScriptForTests(
base::UTF8ToUTF16("window.open('about:blank');"));
content::WebContents* tab2 = new_wc_observer.GetWebContents();
ASSERT_NE(tab1, tab2);
ASSERT_EQ(tab1->GetMainFrame()->GetProcess(),
tab2->GetMainFrame()->GetProcess());
// Tab two shows a dialog.
scoped_refptr<content::MessageLoopRunner> runner =
new content::MessageLoopRunner;
JavaScriptDialogTabHelper* js_helper2 =
JavaScriptDialogTabHelper::FromWebContents(tab2);
js_helper2->SetDialogShownCallbackForTesting(runner->QuitClosure());
tab2->GetMainFrame()->ExecuteJavaScriptForTests(base::UTF8ToUTF16("alert()"));
runner->Run();
// Tab two is closed while the dialog is up.
int tab2_index = browser()->tab_strip_model()->GetIndexOfWebContents(tab2);
browser()->tab_strip_model()->CloseWebContentsAt(tab2_index,
TabStripModel::CLOSE_NONE);
// Try reloading tab one.
tab1->GetController().Reload(content::ReloadType::NORMAL, false);
content::WaitForLoadStop(tab1);
// If the WaitForLoadStop doesn't hang forever, we've passed.
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest,
ClosingPageWithSubframeAlertingDoesntCrash) {
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
JavaScriptDialogTabHelper* js_helper =
JavaScriptDialogTabHelper::FromWebContents(tab);
// A subframe shows a dialog.
std::string dialog_url = "data:text/html,<script>alert(\"hi\");</script>";
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + dialog_url + "';"
"document.body.appendChild(iframe);";
scoped_refptr<content::MessageLoopRunner> runner =
new content::MessageLoopRunner;
js_helper->SetDialogShownCallbackForTesting(runner->QuitClosure());
tab->GetMainFrame()->ExecuteJavaScriptForTests(base::UTF8ToUTF16(script));
runner->Run();
// The tab is closed while the dialog is up.
int tab_index = browser()->tab_strip_model()->GetIndexOfWebContents(tab);
browser()->tab_strip_model()->CloseWebContentsAt(tab_index,
TabStripModel::CLOSE_NONE);
// No crash is good news.
}
class JavaScriptCallbackHelper {
public:
JavaScriptDialogTabHelper::DialogClosedCallback GetCallback() {
return base::BindOnce(&JavaScriptCallbackHelper::DialogClosed,
base::Unretained(this));
}
bool last_success() { return last_success_; }
base::string16 last_input() { return last_input_; }
private:
void DialogClosed(bool success, const base::string16& user_input) {
last_success_ = success;
last_input_ = user_input;
}
bool last_success_;
base::string16 last_input_;
};
// Tests to make sure HandleJavaScriptDialog works correctly.
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest, HandleJavaScriptDialog) {
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* frame = tab->GetMainFrame();
JavaScriptDialogTabHelper* js_helper =
JavaScriptDialogTabHelper::FromWebContents(tab);
JavaScriptCallbackHelper callback_helper;
// alert
bool did_suppress = false;
js_helper->RunJavaScriptDialog(
tab, frame, content::JAVASCRIPT_DIALOG_TYPE_ALERT, base::string16(),
base::string16(), callback_helper.GetCallback(), &did_suppress);
ASSERT_TRUE(js_helper->IsShowingDialogForTesting());
js_helper->HandleJavaScriptDialog(tab, true, nullptr);
ASSERT_FALSE(js_helper->IsShowingDialogForTesting());
ASSERT_TRUE(callback_helper.last_success());
ASSERT_EQ(base::string16(), callback_helper.last_input());
// confirm
for (auto response : {true, false}) {
js_helper->RunJavaScriptDialog(
tab, frame, content::JAVASCRIPT_DIALOG_TYPE_CONFIRM, base::string16(),
base::string16(), callback_helper.GetCallback(), &did_suppress);
ASSERT_TRUE(js_helper->IsShowingDialogForTesting());
js_helper->HandleJavaScriptDialog(tab, response, nullptr);
ASSERT_FALSE(js_helper->IsShowingDialogForTesting());
ASSERT_EQ(response, callback_helper.last_success());
ASSERT_EQ(base::string16(), callback_helper.last_input());
}
// prompt, cancel
js_helper->RunJavaScriptDialog(tab, frame,
content::JAVASCRIPT_DIALOG_TYPE_PROMPT,
base::ASCIIToUTF16("Label"), base::string16(),
callback_helper.GetCallback(), &did_suppress);
ASSERT_TRUE(js_helper->IsShowingDialogForTesting());
js_helper->HandleJavaScriptDialog(tab, false, nullptr);
ASSERT_FALSE(js_helper->IsShowingDialogForTesting());
ASSERT_FALSE(callback_helper.last_success());
ASSERT_EQ(base::string16(), callback_helper.last_input());
base::string16 value1 = base::ASCIIToUTF16("abc");
base::string16 value2 = base::ASCIIToUTF16("123");
// prompt, ok + override
js_helper->RunJavaScriptDialog(tab, frame,
content::JAVASCRIPT_DIALOG_TYPE_PROMPT,
base::ASCIIToUTF16("Label"), value1,
callback_helper.GetCallback(), &did_suppress);
ASSERT_TRUE(js_helper->IsShowingDialogForTesting());
js_helper->HandleJavaScriptDialog(tab, true, &value2);
ASSERT_FALSE(js_helper->IsShowingDialogForTesting());
ASSERT_TRUE(callback_helper.last_success());
ASSERT_EQ(value2, callback_helper.last_input());
// prompt, ok + no override
js_helper->RunJavaScriptDialog(tab, frame,
content::JAVASCRIPT_DIALOG_TYPE_PROMPT,
base::ASCIIToUTF16("Label"), value1,
callback_helper.GetCallback(), &did_suppress);
ASSERT_TRUE(js_helper->IsShowingDialogForTesting());
js_helper->HandleJavaScriptDialog(tab, true, nullptr);
ASSERT_FALSE(js_helper->IsShowingDialogForTesting());
ASSERT_TRUE(callback_helper.last_success());
ASSERT_EQ(value1, callback_helper.last_input());
}
class JavaScriptDialogDismissalCauseTester {
public:
explicit JavaScriptDialogDismissalCauseTester(JavaScriptDialogTest* test)
: tab_(test->browser()->tab_strip_model()->GetActiveWebContents()),
frame_(tab_->GetMainFrame()),
js_helper_(JavaScriptDialogTabHelper::FromWebContents(tab_)) {}
void PopupDialog(content::JavaScriptDialogType type) {
bool did_suppress = false;
js_helper_->RunJavaScriptDialog(
tab_, frame_, type, base::ASCIIToUTF16("Label"),
base::ASCIIToUTF16("abc"), {}, &did_suppress);
}
void ClickDialogButton(bool accept, const base::string16& user_input) {
EXPECT_TRUE(js_helper_->IsShowingDialogForTesting());
js_helper_->ClickDialogButtonForTesting(accept, user_input);
}
void Reload() {
tab_->GetController().Reload(content::ReloadType::NORMAL, false);
content::WaitForLoadStop(tab_);
}
void CallHandleDialog(bool accept, const base::string16* prompt_override) {
EXPECT_TRUE(js_helper_->IsShowingDialogForTesting());
js_helper_->HandleJavaScriptDialog(tab_, accept, prompt_override);
}
void CallCancelDialogs(bool reset_state) {
EXPECT_TRUE(js_helper_->IsShowingDialogForTesting());
js_helper_->CancelDialogs(tab_, reset_state);
}
private:
content::WebContents* tab_;
content::RenderFrameHost* frame_;
JavaScriptDialogTabHelper* js_helper_;
};
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest, DismissalCausePromptAcceptButton) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_PROMPT);
tester.ClickDialogButton(true, base::string16());
histogram_tester.ExpectUniqueSample("JSDialogs.DismissalCause.Prompt",
DismissalCause::kDialogButtonClicked, 1);
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest, DismissalCausePromptCancelButton) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_PROMPT);
tester.ClickDialogButton(false, base::string16());
histogram_tester.ExpectUniqueSample("JSDialogs.DismissalCause.Prompt",
DismissalCause::kDialogButtonClicked, 1);
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest, DismissalCausePromptHandleDialog) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_PROMPT);
tester.CallHandleDialog(true, nullptr);
histogram_tester.ExpectUniqueSample("JSDialogs.DismissalCause.Prompt",
DismissalCause::kHandleDialogCalled, 1);
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest,
DismissalCausePromptCancelDialogs) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_PROMPT);
tester.CallCancelDialogs(false);
histogram_tester.ExpectUniqueSample("JSDialogs.DismissalCause.Prompt",
DismissalCause::kCancelDialogsCalled, 1);
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest,
DismissalCausePromptTabClosedByUser) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_PROMPT);
chrome::CloseTab(browser());
// There are differences in the implementations of Views on different platforms
// that cause different dismissal causes.
#if defined(OS_MACOSX)
// On MacOS 10.13, |kDialogClosed| is logged, while for other versions
// |kCancelDialogsCalled| is logged.
histogram_tester.ExpectTotalCount("JSDialogs.DismissalCause.Prompt", 1);
base::HistogramBase::Count canceled_count = histogram_tester.GetBucketCount(
"JSDialogs.DismissalCause.Prompt",
static_cast<base::HistogramBase::Sample>(
DismissalCause::kCancelDialogsCalled));
base::HistogramBase::Count closed_count = histogram_tester.GetBucketCount(
"JSDialogs.DismissalCause.Prompt",
static_cast<base::HistogramBase::Sample>(DismissalCause::kDialogClosed));
EXPECT_EQ(canceled_count + closed_count, 1);
#else
histogram_tester.ExpectUniqueSample("JSDialogs.DismissalCause.Prompt",
DismissalCause::kDialogClosed, 1);
#endif
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest, DismissalCausePromptTabHidden) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_PROMPT);
chrome::NewTab(browser());
histogram_tester.ExpectUniqueSample("JSDialogs.DismissalCause.Prompt",
DismissalCause::kTabHidden, 1);
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest,
DismissalCausePromptBrowserSwitched) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_PROMPT);
chrome::NewEmptyWindow(browser()->profile());
histogram_tester.ExpectUniqueSample("JSDialogs.DismissalCause.Prompt",
DismissalCause::kBrowserSwitched, 1);
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest, DismissalCausePromptTabNavigated) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_PROMPT);
tester.Reload();
histogram_tester.ExpectUniqueSample("JSDialogs.DismissalCause.Prompt",
DismissalCause::kTabNavigated, 1);
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest,
DismissalCausePromptSubsequentDialogShown) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_PROMPT);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_ALERT);
histogram_tester.ExpectUniqueSample("JSDialogs.DismissalCause.Prompt",
DismissalCause::kSubsequentDialogShown,
1);
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest, NoDismissalAlertTabHidden) {
base::HistogramTester histogram_tester;
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_ALERT);
chrome::NewTab(browser());
histogram_tester.ExpectTotalCount("JSDialogs.DismissalCause.Alert", 0);
}
IN_PROC_BROWSER_TEST_F(JavaScriptDialogTest, DismissalCauseUkm) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
EXPECT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/title1.html");
ui_test_utils::NavigateToURL(browser(), url);
JavaScriptDialogDismissalCauseTester tester(this);
tester.PopupDialog(content::JAVASCRIPT_DIALOG_TYPE_CONFIRM);
tester.ClickDialogButton(true, base::string16());
auto entries = ukm_recorder.GetEntriesByName(
ukm::builders::AbusiveExperienceHeuristic_JavaScriptDialog::kEntryName);
EXPECT_EQ(1u, entries.size());
ukm_recorder.ExpectEntrySourceHasUrl(entries.front(), url);
ukm_recorder.ExpectEntryMetric(
entries.front(),
ukm::builders::AbusiveExperienceHeuristic_JavaScriptDialog::
kDismissalCauseName,
static_cast<int64_t>(DismissalCause::kDialogButtonClicked));
}