1 // Copyright 2014 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 #include "base/metrics/histogram_samples.h" 6 #include "base/prefs/pref_service.h" 7 #include "base/strings/utf_string_conversions.h" 8 #include "base/test/statistics_delta_reader.h" 9 #include "base/time/time.h" 10 #include "chrome/browser/ui/passwords/manage_passwords_bubble.h" 11 #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h" 12 #include "chrome/browser/ui/passwords/manage_passwords_icon.h" 13 #include "chrome/browser/ui/passwords/manage_passwords_icon_mock.h" 14 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller_mock.h" 15 #include "chrome/test/base/chrome_render_view_host_test_harness.h" 16 #include "chrome/test/base/testing_profile.h" 17 #include "components/autofill/core/common/password_form.h" 18 #include "components/password_manager/core/browser/password_form_manager.h" 19 #include "components/password_manager/core/browser/stub_password_manager_client.h" 20 #include "components/password_manager/core/browser/stub_password_manager_driver.h" 21 #include "components/password_manager/core/common/password_manager_ui.h" 22 #include "content/public/test/test_browser_thread_bundle.h" 23 #include "content/public/test/web_contents_tester.h" 24 #include "testing/gmock/include/gmock/gmock.h" 25 #include "testing/gtest/include/gtest/gtest.h" 26 27 namespace { 28 29 const int64 kSlowNavigationDelayInMS = 2000; 30 const int64 kQuickNavigationDelayInMS = 500; 31 32 class MockElapsedTimer : public base::ElapsedTimer { 33 public: 34 MockElapsedTimer() {} 35 virtual base::TimeDelta Elapsed() const OVERRIDE { return delta_; } 36 37 void Advance(int64 ms) { delta_ = base::TimeDelta::FromMilliseconds(ms); } 38 39 private: 40 base::TimeDelta delta_; 41 42 DISALLOW_COPY_AND_ASSIGN(MockElapsedTimer); 43 }; 44 45 } // namespace 46 47 class ManagePasswordsUIControllerTest : public ChromeRenderViewHostTestHarness { 48 public: 49 ManagePasswordsUIControllerTest() {} 50 51 virtual void SetUp() OVERRIDE { 52 ChromeRenderViewHostTestHarness::SetUp(); 53 54 // Create the test UIController here so that it's bound to 55 // |test_web_contents_|, and will be retrieved correctly via 56 // ManagePasswordsUIController::FromWebContents in |controller()|. 57 new ManagePasswordsUIControllerMock(web_contents()); 58 59 test_form_.origin = GURL("http://example.com"); 60 61 // We need to be on a "webby" URL for most tests. 62 content::WebContentsTester::For(web_contents()) 63 ->NavigateAndCommit(GURL("http://example.com")); 64 } 65 66 autofill::PasswordForm& test_form() { return test_form_; } 67 68 ManagePasswordsUIControllerMock* controller() { 69 return static_cast<ManagePasswordsUIControllerMock*>( 70 ManagePasswordsUIController::FromWebContents(web_contents())); 71 } 72 73 private: 74 autofill::PasswordForm test_form_; 75 }; 76 77 TEST_F(ManagePasswordsUIControllerTest, DefaultState) { 78 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, controller()->state()); 79 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 80 EXPECT_EQ(GURL::EmptyGURL(), controller()->origin()); 81 82 ManagePasswordsIconMock mock; 83 controller()->UpdateIconAndBubbleState(&mock); 84 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state()); 85 } 86 87 TEST_F(ManagePasswordsUIControllerTest, PasswordAutofilled) { 88 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 89 autofill::PasswordFormMap map; 90 map[kTestUsername] = &test_form(); 91 controller()->OnPasswordAutofilled(map); 92 93 EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state()); 94 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 95 EXPECT_EQ(test_form().origin, controller()->origin()); 96 97 ManagePasswordsIconMock mock; 98 controller()->UpdateIconAndBubbleState(&mock); 99 EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state()); 100 } 101 102 TEST_F(ManagePasswordsUIControllerTest, PasswordSubmitted) { 103 password_manager::StubPasswordManagerClient client; 104 password_manager::StubPasswordManagerDriver driver; 105 password_manager::PasswordFormManager* test_form_manager = 106 new password_manager::PasswordFormManager( 107 NULL, &client, &driver, test_form(), false); 108 controller()->OnPasswordSubmitted(test_form_manager); 109 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE, 110 controller()->state()); 111 EXPECT_TRUE(controller()->PasswordPendingUserDecision()); 112 113 // TODO(mkwst): This should be the value of test_form().origin, but 114 // it's being masked by the stub implementation of 115 // ManagePasswordsUIControllerMock::PendingCredentials. 116 EXPECT_EQ(GURL::EmptyGURL(), controller()->origin()); 117 118 ManagePasswordsIconMock mock; 119 controller()->UpdateIconAndBubbleState(&mock); 120 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, mock.state()); 121 } 122 123 TEST_F(ManagePasswordsUIControllerTest, QuickNavigations) { 124 password_manager::StubPasswordManagerClient client; 125 password_manager::StubPasswordManagerDriver driver; 126 password_manager::PasswordFormManager* test_form_manager = 127 new password_manager::PasswordFormManager( 128 NULL, &client, &driver, test_form(), false); 129 controller()->OnPasswordSubmitted(test_form_manager); 130 ManagePasswordsIconMock mock; 131 controller()->UpdateIconAndBubbleState(&mock); 132 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, mock.state()); 133 134 // Fake-navigate within a second. We expect the bubble's state to persist 135 // if a navigation occurs too quickly for a user to reasonably have been 136 // able to interact with the bubble. This happens on `accounts.google.com`, 137 // for instance. 138 scoped_ptr<MockElapsedTimer> timer(new MockElapsedTimer()); 139 timer->Advance(kQuickNavigationDelayInMS); 140 controller()->SetTimer(timer.release()); 141 controller()->DidNavigateMainFrame(content::LoadCommittedDetails(), 142 content::FrameNavigateParams()); 143 controller()->UpdateIconAndBubbleState(&mock); 144 145 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, mock.state()); 146 } 147 148 TEST_F(ManagePasswordsUIControllerTest, SlowNavigations) { 149 password_manager::StubPasswordManagerClient client; 150 password_manager::StubPasswordManagerDriver driver; 151 password_manager::PasswordFormManager* test_form_manager = 152 new password_manager::PasswordFormManager( 153 NULL, &client, &driver, test_form(), false); 154 controller()->OnPasswordSubmitted(test_form_manager); 155 ManagePasswordsIconMock mock; 156 controller()->UpdateIconAndBubbleState(&mock); 157 EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, mock.state()); 158 159 // Fake-navigate after a second. We expect the bubble's state to be reset 160 // if a navigation occurs after this limit. 161 scoped_ptr<MockElapsedTimer> timer(new MockElapsedTimer()); 162 timer->Advance(kSlowNavigationDelayInMS); 163 controller()->SetTimer(timer.release()); 164 controller()->DidNavigateMainFrame(content::LoadCommittedDetails(), 165 content::FrameNavigateParams()); 166 controller()->UpdateIconAndBubbleState(&mock); 167 168 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state()); 169 } 170 171 TEST_F(ManagePasswordsUIControllerTest, PasswordSubmittedToNonWebbyURL) { 172 // Navigate to a non-webby URL, then see what happens! 173 content::WebContentsTester::For(web_contents()) 174 ->NavigateAndCommit(GURL("chrome://sign-in")); 175 176 password_manager::StubPasswordManagerClient client; 177 password_manager::StubPasswordManagerDriver driver; 178 password_manager::PasswordFormManager* test_form_manager = 179 new password_manager::PasswordFormManager( 180 NULL, &client, &driver, test_form(), false); 181 controller()->OnPasswordSubmitted(test_form_manager); 182 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, controller()->state()); 183 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 184 185 // TODO(mkwst): This should be the value of test_form().origin, but 186 // it's being masked by the stub implementation of 187 // ManagePasswordsUIControllerMock::PendingCredentials. 188 EXPECT_EQ(GURL::EmptyGURL(), controller()->origin()); 189 190 ManagePasswordsIconMock mock; 191 controller()->UpdateIconAndBubbleState(&mock); 192 EXPECT_EQ(password_manager::ui::INACTIVE_STATE, mock.state()); 193 } 194 195 TEST_F(ManagePasswordsUIControllerTest, BlacklistBlockedAutofill) { 196 test_form().blacklisted_by_user = true; 197 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 198 autofill::PasswordFormMap map; 199 map[kTestUsername] = &test_form(); 200 controller()->OnBlacklistBlockedAutofill(map); 201 202 EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, controller()->state()); 203 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 204 EXPECT_EQ(test_form().origin, controller()->origin()); 205 206 ManagePasswordsIconMock mock; 207 controller()->UpdateIconAndBubbleState(&mock); 208 EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state()); 209 } 210 211 TEST_F(ManagePasswordsUIControllerTest, ClickedUnblacklist) { 212 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 213 autofill::PasswordFormMap map; 214 map[kTestUsername] = &test_form(); 215 controller()->OnBlacklistBlockedAutofill(map); 216 controller()->UnblacklistSite(); 217 218 EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state()); 219 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 220 EXPECT_EQ(test_form().origin, controller()->origin()); 221 222 ManagePasswordsIconMock mock; 223 controller()->UpdateIconAndBubbleState(&mock); 224 EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state()); 225 } 226 227 TEST_F(ManagePasswordsUIControllerTest, UnblacklistedElsewhere) { 228 test_form().blacklisted_by_user = true; 229 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 230 autofill::PasswordFormMap map; 231 map[kTestUsername] = &test_form(); 232 controller()->OnBlacklistBlockedAutofill(map); 233 234 password_manager::PasswordStoreChange change( 235 password_manager::PasswordStoreChange::REMOVE, test_form()); 236 password_manager::PasswordStoreChangeList list(1, change); 237 controller()->OnLoginsChanged(list); 238 239 EXPECT_EQ(password_manager::ui::MANAGE_STATE, controller()->state()); 240 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 241 EXPECT_EQ(test_form().origin, controller()->origin()); 242 243 ManagePasswordsIconMock mock; 244 controller()->UpdateIconAndBubbleState(&mock); 245 EXPECT_EQ(password_manager::ui::MANAGE_STATE, mock.state()); 246 } 247 248 TEST_F(ManagePasswordsUIControllerTest, BlacklistedElsewhere) { 249 base::string16 kTestUsername = base::ASCIIToUTF16("test_username"); 250 autofill::PasswordFormMap map; 251 map[kTestUsername] = &test_form(); 252 controller()->OnPasswordAutofilled(map); 253 254 test_form().blacklisted_by_user = true; 255 password_manager::PasswordStoreChange change( 256 password_manager::PasswordStoreChange::ADD, test_form()); 257 password_manager::PasswordStoreChangeList list(1, change); 258 controller()->OnLoginsChanged(list); 259 260 EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, controller()->state()); 261 EXPECT_FALSE(controller()->PasswordPendingUserDecision()); 262 EXPECT_EQ(test_form().origin, controller()->origin()); 263 264 ManagePasswordsIconMock mock; 265 controller()->UpdateIconAndBubbleState(&mock); 266 EXPECT_EQ(password_manager::ui::BLACKLIST_STATE, mock.state()); 267 } 268 269