blob: efabc0a9dc99363e0458a264f66dd7e346ccbc44 [file] [log] [blame]
// Copyright 2014 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/command_line.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/password_manager/password_manager_interactive_test_base.h"
#include "chrome/browser/password_manager/password_store_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/passwords/password_generation_popup_observer.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/password_manager/core/browser/new_password_form_manager.h"
#include "components/password_manager/core/browser/password_generation_frame_helper.h"
#include "components/password_manager/core/browser/password_manager_util.h"
#include "components/password_manager/core/browser/test_password_store.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/keycodes/keyboard_codes.h"
namespace {
class TestPopupObserver : public PasswordGenerationPopupObserver {
public:
enum class GenerationPopup {
kShown,
kHidden,
};
TestPopupObserver() = default;
~TestPopupObserver() = default;
void OnPopupShown(
PasswordGenerationPopupController::GenerationUIState state) override {
popup_showing_ = GenerationPopup::kShown;
state_ = state;
MaybeQuitRunLoop();
}
void OnPopupHidden() override {
popup_showing_ = GenerationPopup::kHidden;
MaybeQuitRunLoop();
}
bool popup_showing() const {
return popup_showing_ == GenerationPopup::kShown;
}
PasswordGenerationPopupController::GenerationUIState state() const {
return state_;
}
// Waits until the popup is in specified status.
void WaitForStatus(GenerationPopup status) {
if (status == popup_showing_)
return;
base::RunLoop run_loop;
run_loop_ = &run_loop;
run_loop_->Run();
EXPECT_EQ(popup_showing_, status);
}
// Waits until the popup is either shown or hidden.
void WaitForStatusChange() {
base::RunLoop run_loop;
run_loop_ = &run_loop;
run_loop_->Run();
}
private:
void MaybeQuitRunLoop() {
if (run_loop_) {
run_loop_->Quit();
run_loop_ = nullptr;
}
}
// The loop to be stopped after the popup state change.
base::RunLoop* run_loop_ = nullptr;
GenerationPopup popup_showing_ = GenerationPopup::kHidden;
PasswordGenerationPopupController::GenerationUIState state_ =
PasswordGenerationPopupController::kOfferGeneration;
DISALLOW_COPY_AND_ASSIGN(TestPopupObserver);
};
enum ReturnCodes { // Possible results of the JavaScript code.
RETURN_CODE_OK,
RETURN_CODE_NO_ELEMENT,
RETURN_CODE_INVALID,
};
} // namespace
class PasswordGenerationInteractiveTest
: public PasswordManagerInteractiveTestBase {
public:
void SetUpOnMainThread() override {
PasswordManagerBrowserTestBase::SetUpOnMainThread();
// Disable Autofill requesting access to AddressBook data. This will cause
// the tests to hang on Mac.
autofill::test::DisableSystemServices(browser()->profile()->GetPrefs());
// Set observer for popup.
ChromePasswordManagerClient* client =
ChromePasswordManagerClient::FromWebContents(WebContents());
client->SetTestObserver(&observer_);
// The base class should enable password generation.
ASSERT_NE(password_manager::NOT_SYNCING, client->GetPasswordSyncState());
password_manager::NewPasswordFormManager::
set_wait_for_server_predictions_for_filling(false);
NavigateToFile("/password/signup_form_new_password.html");
}
void TearDownOnMainThread() override {
PasswordManagerBrowserTestBase::TearDownOnMainThread();
autofill::test::ReenableSystemServices();
}
// Waits until the value of the field with id |field_id| becomes non-empty.
void WaitForNonEmptyFieldValue(const std::string& field_id) {
const std::string script = base::StringPrintf(
"element = document.getElementById('%s');"
"if (!element) {"
" setTimeout(window.domAutomationController.send(%d), 0);"
"}"
"if (element.value) {"
" setTimeout(window.domAutomationController.send(%d), 0); "
"} else {"
" element.onchange = function() {"
" if (element.value) {"
" window.domAutomationController.send(%d);"
" }"
" }"
"}",
field_id.c_str(), RETURN_CODE_NO_ELEMENT, RETURN_CODE_OK,
RETURN_CODE_OK);
int return_value = RETURN_CODE_INVALID;
ASSERT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractInt(
RenderFrameHost(), script, &return_value));
EXPECT_EQ(RETURN_CODE_OK, return_value);
}
std::string GetFocusedElement() {
std::string focused_element;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
WebContents(),
"window.domAutomationController.send("
" document.activeElement.id)",
&focused_element));
return focused_element;
}
void FocusPasswordField() {
ASSERT_TRUE(content::ExecuteScript(
WebContents(), "document.getElementById('password_field').focus()"));
}
void FocusUsernameField() {
ASSERT_TRUE(content::ExecuteScript(
WebContents(), "document.getElementById('username_field').focus();"));
}
void SendKeyToPopup(ui::KeyboardCode key) {
content::NativeWebKeyboardEvent event(
blink::WebKeyboardEvent::kRawKeyDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
event.windows_key_code = key;
WebContents()->GetRenderViewHost()->GetWidget()->ForwardKeyboardEvent(
event);
}
bool GenerationPopupShowing() {
return observer_.popup_showing() &&
observer_.state() ==
PasswordGenerationPopupController::kOfferGeneration;
}
bool EditingPopupShowing() {
return observer_.popup_showing() &&
observer_.state() ==
PasswordGenerationPopupController::kEditGeneratedPassword;
}
void WaitForStatus(TestPopupObserver::GenerationPopup status) {
observer_.WaitForStatus(status);
}
void WaitForPopupStatusChange() { observer_.WaitForStatusChange(); }
private:
TestPopupObserver observer_;
};
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
PopupShownAndPasswordSelected) {
FocusPasswordField();
EXPECT_TRUE(GenerationPopupShowing());
base::HistogramTester histogram_tester;
SendKeyToPopup(ui::VKEY_DOWN);
SendKeyToPopup(ui::VKEY_RETURN);
// Selecting the password should fill the field and move focus to the
// submit button.
WaitForNonEmptyFieldValue("password_field");
EXPECT_FALSE(GenerationPopupShowing());
EXPECT_FALSE(EditingPopupShowing());
EXPECT_EQ("input_submit_button", GetFocusedElement());
// Re-focusing the password field should show the editing popup.
FocusPasswordField();
EXPECT_TRUE(EditingPopupShowing());
// The metrics are recorded when the form manager is destroyed. Closing the
// tab enforces it.
CloseAllBrowsers();
histogram_tester.ExpectUniqueSample(
"PasswordGeneration.UserDecision",
password_manager::PasswordFormMetricsRecorder::GeneratedPasswordStatus::
kPasswordAccepted,
1);
}
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
PopupShownAutomaticallyAndPasswordErased) {
FocusPasswordField();
EXPECT_TRUE(GenerationPopupShowing());
SendKeyToPopup(ui::VKEY_DOWN);
SendKeyToPopup(ui::VKEY_RETURN);
// Wait until the password is filled.
WaitForNonEmptyFieldValue("password_field");
// Re-focusing the password field should show the editing popup.
FocusPasswordField();
EXPECT_TRUE(EditingPopupShowing());
// Delete the password. The generation prompt should be visible.
base::HistogramTester histogram_tester;
SimulateUserDeletingFieldContent("password_field");
WaitForPopupStatusChange();
EXPECT_FALSE(EditingPopupShowing());
EXPECT_TRUE(GenerationPopupShowing());
// The metrics are recorded on navigation when the frame is destroyed.
NavigateToFile("/password/done.html");
histogram_tester.ExpectUniqueSample(
"PasswordGeneration.UserDecision",
password_manager::PasswordFormMetricsRecorder::GeneratedPasswordStatus::
kPasswordDeleted,
1);
}
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
PopupShownManuallyAndPasswordErased) {
NavigateToFile("/password/password_form.html");
FocusPasswordField();
EXPECT_FALSE(GenerationPopupShowing());
// The same flow happens when user generates a password from the context menu.
password_manager_util::UserTriggeredManualGenerationFromContextMenu(
ChromePasswordManagerClient::FromWebContents(WebContents()));
WaitForStatus(TestPopupObserver::GenerationPopup::kShown);
EXPECT_TRUE(GenerationPopupShowing());
SendKeyToPopup(ui::VKEY_DOWN);
SendKeyToPopup(ui::VKEY_RETURN);
// Wait until the password is filled.
WaitForNonEmptyFieldValue("password_field");
// Re-focusing the password field should show the editing popup.
FocusPasswordField();
EXPECT_TRUE(EditingPopupShowing());
// Delete the password. The generation prompt should not be visible.
SimulateUserDeletingFieldContent("password_field");
WaitForStatus(TestPopupObserver::GenerationPopup::kHidden);
EXPECT_FALSE(EditingPopupShowing());
EXPECT_FALSE(GenerationPopupShowing());
}
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
PopupShownAndDismissed) {
FocusPasswordField();
EXPECT_TRUE(GenerationPopupShowing());
FocusUsernameField();
// Popup is dismissed.
WaitForStatus(TestPopupObserver::GenerationPopup::kHidden);
}
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
PopupShownAndDismissedByKeyPress) {
FocusPasswordField();
EXPECT_TRUE(GenerationPopupShowing());
SendKeyToPopup(ui::VKEY_ESCAPE);
// Popup is dismissed.
EXPECT_FALSE(GenerationPopupShowing());
}
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
PopupShownAndDismissedByScrolling) {
FocusPasswordField();
EXPECT_TRUE(GenerationPopupShowing());
ASSERT_TRUE(
content::ExecuteScript(WebContents(), "window.scrollTo(100, 0);"));
EXPECT_FALSE(GenerationPopupShowing());
}
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
GenerationTriggeredInIFrame) {
NavigateToFile("/password/framed_signup_form.html");
// Execute the script in the context of the iframe so that it kinda receives a
// user gesture.
std::vector<content::RenderFrameHost*> frames = WebContents()->GetAllFrames();
ASSERT_EQ(2u, frames.size());
ASSERT_TRUE(frames[0] == RenderFrameHost());
std::string focus_script =
"document.getElementById('password_field').focus();";
ASSERT_TRUE(content::ExecuteScript(frames[1], focus_script));
EXPECT_TRUE(GenerationPopupShowing());
}
// https://crbug.com/791389
IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
DISABLED_AutoSavingGeneratedPassword) {
scoped_refptr<password_manager::TestPasswordStore> password_store =
static_cast<password_manager::TestPasswordStore*>(
PasswordStoreFactory::GetForProfile(
browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS).get());
FocusPasswordField();
EXPECT_TRUE(GenerationPopupShowing());
SendKeyToPopup(ui::VKEY_DOWN);
SendKeyToPopup(ui::VKEY_RETURN);
// Change username.
FocusUsernameField();
content::SimulateKeyPress(WebContents(), ui::DomKey::FromCharacter('U'),
ui::DomCode::US_U, ui::VKEY_U, false, false, false,
false);
content::SimulateKeyPress(WebContents(), ui::DomKey::FromCharacter('N'),
ui::DomCode::US_N, ui::VKEY_N, false, false, false,
false);
// Submit form.
NavigationObserver observer(WebContents());
std::string submit_script =
"document.getElementById('input_submit_button').click()";
ASSERT_TRUE(content::ExecuteScript(WebContents(), submit_script));
observer.Wait();
WaitForPasswordStore();
EXPECT_FALSE(password_store->IsEmpty());
// Make sure the username is correct.
password_manager::TestPasswordStore::PasswordMap stored_passwords =
password_store->stored_passwords();
EXPECT_EQ(1u, stored_passwords.size());
EXPECT_EQ(1u, stored_passwords.begin()->second.size());
EXPECT_EQ(base::UTF8ToUTF16("UN"),
(stored_passwords.begin()->second)[0].username_value);
}