diff options
author | gcasto@chromium.org <gcasto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-31 23:12:23 +0000 |
---|---|---|
committer | gcasto@chromium.org <gcasto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-31 23:12:23 +0000 |
commit | 3cbdf9363d3c014d5ff484a30ab36c6bc9972bc9 (patch) | |
tree | 4a8d97ff50b587f5e95ea3826cec76f9b79474b9 | |
parent | 598ed4879c529168b8ca5186eed85ec691ee09ea (diff) | |
download | chromium_src-3cbdf9363d3c014d5ff484a30ab36c6bc9972bc9.zip chromium_src-3cbdf9363d3c014d5ff484a30ab36c6bc9972bc9.tar.gz chromium_src-3cbdf9363d3c014d5ff484a30ab36c6bc9972bc9.tar.bz2 |
Re-land r248110 "[Password Generation] Enable new UI"
Replaces key icon and associated bubble with autofill style dropdown. Changes include
- Update PasswordGenerationAgentTest
- Add interactive_uitest for new UI.
- Sync edits to confirmation password field(s).
- Update UMA stats to more accurately reflect user choices.
Updating the generation popup to match the latest mocks and implement the
editing popup will come in a later CL.
This reverts r248119 and fixes a use-after-free in
PasswordGenerationPopupControllerImpl::PossiblyAcceptPassword()
R=asvitkine@chromium.org, estade@chromium.org
TBR=kenrb@chromium.org, sky@chromium.org, torne@chromium.org
BUG=318977
Review URL: https://codereview.chromium.org/151503006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@248297 0039d316-1c4b-4281-b951-d872f2087c98
29 files changed, 703 insertions, 172 deletions
diff --git a/android_webview/renderer/aw_content_renderer_client.cc b/android_webview/renderer/aw_content_renderer_client.cc index 224e1ed..628d284 100644 --- a/android_webview/renderer/aw_content_renderer_client.cc +++ b/android_webview/renderer/aw_content_renderer_client.cc @@ -139,7 +139,7 @@ void AwContentRendererClient::RenderViewCreated( // autofill agent to store a weakptr). autofill::PasswordAutofillAgent* password_autofill_agent = new autofill::PasswordAutofillAgent(render_view); - new autofill::AutofillAgent(render_view, password_autofill_agent); + new autofill::AutofillAgent(render_view, password_autofill_agent, NULL); } std::string AwContentRendererClient::GetDefaultEncoding() { diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 1fc665f..4863fbe 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -1545,7 +1545,7 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches( autofill::switches::kEnableIgnoreAutocompleteOff, autofill::switches::kEnableInteractiveAutocomplete, autofill::switches::kEnablePasswordGeneration, - autofill::switches::kNoAutofillNecessaryForPasswordGeneration, + autofill::switches::kLocalHeuristicsOnlyForPasswordGeneration, extensions::switches::kAllowHTTPBackgroundPage, extensions::switches::kAllowLegacyExtensionManifests, extensions::switches::kAllowScriptingGallery, diff --git a/chrome/browser/password_manager/password_generation_interactive_uitest.cc b/chrome/browser/password_manager/password_generation_interactive_uitest.cc new file mode 100644 index 0000000..fff3b09 --- /dev/null +++ b/chrome/browser/password_manager/password_generation_interactive_uitest.cc @@ -0,0 +1,147 @@ +// 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 "chrome/browser/password_manager/password_generation_manager.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/autofill/password_generation_popup_observer.h" +#include "chrome/browser/ui/browser.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_switches.h" +#include "content/public/browser/render_view_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/keycodes/keyboard_codes.h" + +namespace { + +class TestPopupObserver : public autofill::PasswordGenerationPopupObserver { + public: + TestPopupObserver() + : popup_showing_(false) {} + virtual ~TestPopupObserver() {} + + virtual void OnPopupShown() OVERRIDE { + popup_showing_ = true; + } + + virtual void OnPopupHidden() OVERRIDE { + popup_showing_ = false; + } + + bool popup_showing() { return popup_showing_; } + + private: + bool popup_showing_; +}; + +} // namespace + +class PasswordGenerationInteractiveTest : public InProcessBrowserTest { + public: + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + // Make sure the feature is enabled. + command_line->AppendSwitch(autofill::switches::kEnablePasswordGeneration); + + // Don't require ping from autofill or blacklist checking. + command_line->AppendSwitch( + autofill::switches::kLocalHeuristicsOnlyForPasswordGeneration); + } + + virtual void SetUpOnMainThread() OVERRIDE { + // Disable Autofill requesting access to AddressBook data. This will cause + // test tests to hang on Mac. + autofill::test::DisableSystemServices(browser()->profile()); + + // Set observer for popup. + PasswordGenerationManager* generation_manager = + PasswordGenerationManager::FromWebContents(GetWebContents()); + generation_manager->SetTestObserver(&observer_); + + ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); + GURL url = embedded_test_server()->GetURL("/password/signup_form.html"); + ui_test_utils::NavigateToURL(browser(), url); + } + + virtual void CleanUpOnMainThread() OVERRIDE { + // Cleanup UI. + PasswordGenerationManager* generation_manager = + PasswordGenerationManager::FromWebContents(GetWebContents()); + generation_manager->HidePopup(); + } + + content::WebContents* GetWebContents() { + return browser()->tab_strip_model()->GetActiveWebContents(); + } + + content::RenderViewHost* GetRenderViewHost() { + return GetWebContents()->GetRenderViewHost(); + } + + std::string GetFieldValue(const std::string& field_id) { + std::string value; + EXPECT_TRUE(content::ExecuteScriptAndExtractString( + GetRenderViewHost(), + "window.domAutomationController.send(" + " document.getElementById('" + field_id + "').value);", + &value)); + return value; + } + + void FocusPasswordField() { + ASSERT_TRUE(content::ExecuteScript( + GetRenderViewHost(), + "document.getElementById('password_field').focus()")); + } + + void SendKeyToPopup(ui::KeyboardCode key) { + content::NativeWebKeyboardEvent event; + event.windowsKeyCode = key; + event.type = blink::WebKeyboardEvent::RawKeyDown; + GetRenderViewHost()->ForwardKeyboardEvent(event); + } + + bool popup_showing() { + return observer_.popup_showing(); + } + + private: + TestPopupObserver observer_; +}; + +#if defined(USE_AURA) +// Enabled on these platforms +#define MAYBE_PopupShownAndPasswordSelected PopupShownAndPasswordSelected +#define MAYBE_PopupShownAndDismissed PopupShownAndDismissed +#else +// Popup not enabled for these platforms yet. +#define MAYBE_PopupShownAndPasswordSelected DISABLED_PopupShownAndPasswordSelected +#define MAYBE_PopupShownAndDismissed DISABLED_PopupShownAndDismissed +#endif + +IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest, + MAYBE_PopupShownAndPasswordSelected) { + FocusPasswordField(); + EXPECT_TRUE(popup_showing()); + SendKeyToPopup(ui::VKEY_DOWN); + SendKeyToPopup(ui::VKEY_RETURN); + + EXPECT_FALSE(GetFieldValue("password_field").empty()); +} + +IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest, + MAYBE_PopupShownAndDismissed) { + FocusPasswordField(); + EXPECT_TRUE(popup_showing()); + + SendKeyToPopup(ui::VKEY_ESCAPE); + + // Popup is dismissed. + EXPECT_FALSE(popup_showing()); +} diff --git a/chrome/browser/password_manager/password_generation_manager.cc b/chrome/browser/password_manager/password_generation_manager.cc index 0528120..d808b2f 100644 --- a/chrome/browser/password_manager/password_generation_manager.cc +++ b/chrome/browser/password_manager/password_generation_manager.cc @@ -8,6 +8,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_service_factory.h" +#include "chrome/browser/ui/autofill/password_generation_popup_controller_impl.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_window.h" @@ -21,6 +22,7 @@ #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_view.h" #include "ipc/ipc_message_macros.h" #include "ui/gfx/rect.h" @@ -28,10 +30,16 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(PasswordGenerationManager); PasswordGenerationManager::PasswordGenerationManager( content::WebContents* contents) - : content::WebContentsObserver(contents) {} + : content::WebContentsObserver(contents), + observer_(NULL) {} PasswordGenerationManager::~PasswordGenerationManager() {} +void PasswordGenerationManager::SetTestObserver( + autofill::PasswordGenerationPopupObserver* observer) { + observer_ = observer; +} + void PasswordGenerationManager::DetectAccountCreationForms( const std::vector<autofill::FormStructure*>& forms) { std::vector<autofill::FormData> account_creation_forms; @@ -58,6 +66,10 @@ bool PasswordGenerationManager::OnMessageReceived(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(PasswordGenerationManager, message) IPC_MESSAGE_HANDLER(AutofillHostMsg_ShowPasswordGenerationPopup, OnShowPasswordGenerationPopup) + IPC_MESSAGE_HANDLER(AutofillHostMsg_ShowPasswordEditingPopup, + OnShowPasswordEditingPopup) + IPC_MESSAGE_HANDLER(AutofillHostMsg_HidePasswordGenerationPopup, + OnHidePasswordGenerationPopup) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -102,17 +114,50 @@ void PasswordGenerationManager::SendAccountCreationFormsToRenderer( host->GetRoutingID(), forms)); } +gfx::RectF PasswordGenerationManager::GetBoundsInScreenSpace( + const gfx::RectF& bounds) { + gfx::Rect client_area; + web_contents()->GetView()->GetContainerBounds(&client_area); + return bounds + client_area.OffsetFromOrigin(); +} + void PasswordGenerationManager::OnShowPasswordGenerationPopup( - const gfx::Rect& bounds, + const gfx::RectF& bounds, int max_length, const autofill::PasswordForm& form) { -#if defined(OS_ANDROID) - NOTIMPLEMENTED(); -#else + // TODO(gcasto): Validate data in PasswordForm. + + // Only implemented for Aura right now. +#if defined(USE_AURA) + // Convert element_bounds to be in screen space. + gfx::RectF element_bounds_in_screen_space = GetBoundsInScreenSpace(bounds); + password_generator_.reset(new autofill::PasswordGenerator(max_length)); - Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); - browser->window()->ShowPasswordGenerationBubble(bounds, - form, - password_generator_.get()); -#endif // #if defined(OS_ANDROID) + + popup_controller_ = + autofill::PasswordGenerationPopupControllerImpl::GetOrCreate( + popup_controller_, + element_bounds_in_screen_space, + form, + password_generator_.get(), + PasswordManager::FromWebContents(web_contents()), + observer_, + web_contents(), + web_contents()->GetView()->GetNativeView()); + popup_controller_->Show(); +#endif // #if defined(USE_AURA) +} + +void PasswordGenerationManager::OnShowPasswordEditingPopup( + const gfx::RectF& bounds) { + // TODO(gcasto): Enable this. +} + +void PasswordGenerationManager::OnHidePasswordGenerationPopup() { + HidePopup(); +} + +void PasswordGenerationManager::HidePopup() { + if (popup_controller_) + popup_controller_->HideAndDestroy(); } diff --git a/chrome/browser/password_manager/password_generation_manager.h b/chrome/browser/password_manager/password_generation_manager.h index 6d0a7ff..3eb4cbd 100644 --- a/chrome/browser/password_manager/password_generation_manager.h +++ b/chrome/browser/password_manager/password_generation_manager.h @@ -7,13 +7,17 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" +#include "ui/gfx/rect.h" namespace autofill { struct FormData; class FormStructure; class PasswordGenerator; +class PasswordGenerationPopupControllerImpl; +class PasswordGenerationPopupObserver; struct PasswordForm; } @@ -46,6 +50,12 @@ class PasswordGenerationManager void DetectAccountCreationForms( const std::vector<autofill::FormStructure*>& forms); + // Hide any visible password generation related popups. + void HidePopup(); + + // Observer for PasswordGenerationPopup events. Used for testing. + void SetTestObserver(autofill::PasswordGenerationPopupObserver* observer); + protected: explicit PasswordGenerationManager(content::WebContents* contents); @@ -65,16 +75,33 @@ class PasswordGenerationManager content::RenderViewHost* host, const std::vector<autofill::FormData>& forms); - // Causes the password generation bubble UI to be shown for the specified - // form. The popup will be anchored at |icon_bounds|. The generated - // password will be no longer than |max_length|. - void OnShowPasswordGenerationPopup(const gfx::Rect& icon_bounds, + // Given |bounds| in the renderers coordinate system, return the same bounds + // in the screens coordinate system. + gfx::RectF GetBoundsInScreenSpace(const gfx::RectF& bounds); + + // Causes the password generation UI to be shown for the specified form. + // The popup will be anchored at |element_bounds|. The generated password + // will be no longer than |max_length|. + void OnShowPasswordGenerationPopup(const gfx::RectF& element_bounds, int max_length, const autofill::PasswordForm& form); + // Causes the password editing UI to be shown anchored at |element_bounds|. + void OnShowPasswordEditingPopup(const gfx::RectF& element_bounds); + + // Hide any visible UI. + void OnHidePasswordGenerationPopup(); + + // Observer for password generation popup. + autofill::PasswordGenerationPopupObserver* observer_; + // Controls how passwords are generated. scoped_ptr<autofill::PasswordGenerator> password_generator_; + // Controls the popup + base::WeakPtr< + autofill::PasswordGenerationPopupControllerImpl> popup_controller_; + DISALLOW_COPY_AND_ASSIGN(PasswordGenerationManager); }; diff --git a/chrome/browser/ui/autofill/password_generation_popup_controller_impl.cc b/chrome/browser/ui/autofill/password_generation_popup_controller_impl.cc index 8d73906..1aa5dd6 100644 --- a/chrome/browser/ui/autofill/password_generation_popup_controller_impl.cc +++ b/chrome/browser/ui/autofill/password_generation_popup_controller_impl.cc @@ -105,7 +105,7 @@ bool PasswordGenerationPopupControllerImpl::HandleKeyPressEvent( return true; case ui::VKEY_RETURN: case ui::VKEY_TAB: - // We supress tab if the password is selected because we will + // We suppress tab if the password is selected because we will // automatically advance focus anyway. return PossiblyAcceptPassword(); default: @@ -114,10 +114,12 @@ bool PasswordGenerationPopupControllerImpl::HandleKeyPressEvent( } bool PasswordGenerationPopupControllerImpl::PossiblyAcceptPassword() { - if (password_selected_) - PasswordAccepted(); + if (password_selected_) { + PasswordAccepted(); // This will delete |this|. + return true; + } - return password_selected_; + return false; } void PasswordGenerationPopupControllerImpl::PasswordSelected(bool selected) { @@ -207,6 +209,10 @@ void PasswordGenerationPopupControllerImpl::Show() { observer_->OnPopupShown(); } +void PasswordGenerationPopupControllerImpl::HideAndDestroy() { + Hide(); +} + void PasswordGenerationPopupControllerImpl::Hide() { controller_common_.RemoveKeyPressCallback(); diff --git a/chrome/browser/ui/autofill/password_generation_popup_controller_impl.h b/chrome/browser/ui/autofill/password_generation_popup_controller_impl.h index 686d786..d36c884 100644 --- a/chrome/browser/ui/autofill/password_generation_popup_controller_impl.h +++ b/chrome/browser/ui/autofill/password_generation_popup_controller_impl.h @@ -59,6 +59,9 @@ class PasswordGenerationPopupControllerImpl // Does not update the view if one is already showing. void Show(); + // Hides the popup and destroys |this|. + void HideAndDestroy(); + // Accessors. content::WebContents* web_contents() { return controller_common_.web_contents(); diff --git a/chrome/browser/ui/autofill/password_generation_popup_view.cc b/chrome/browser/ui/autofill/password_generation_popup_view.cc new file mode 100644 index 0000000..dd250ae --- /dev/null +++ b/chrome/browser/ui/autofill/password_generation_popup_view.cc @@ -0,0 +1,21 @@ +// 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 <cstddef> + +#include "chrome/browser/ui/autofill/password_generation_popup_view.h" + +// Non-aura is not implemented yet. +#if !defined(USE_AURA) + +namespace autofill { + +PasswordGenerationPopupView* PasswordGenerationPopupView::Create( + PasswordGenerationPopupController* controller) { + return NULL; +} + +} // namespace autofill + +#endif // #if !defined(USE_AURA) diff --git a/chrome/browser/ui/autofill/tab_autofill_manager_delegate.cc b/chrome/browser/ui/autofill/tab_autofill_manager_delegate.cc index 5e306e8..9c3dfdb 100644 --- a/chrome/browser/ui/autofill/tab_autofill_manager_delegate.cc +++ b/chrome/browser/ui/autofill/tab_autofill_manager_delegate.cc @@ -144,6 +144,13 @@ void TabAutofillManagerDelegate::UpdateAutofillPopupDataListValues( void TabAutofillManagerDelegate::HideAutofillPopup() { if (popup_controller_.get()) popup_controller_->Hide(); + + // Password generation popups behave in the same fashion and should also + // be hidden. + PasswordGenerationManager* generation_manager = + PasswordGenerationManager::FromWebContents(web_contents_); + if (generation_manager) + generation_manager->HidePopup(); } bool TabAutofillManagerDelegate::IsAutocompleteEnabled() { diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi index 8f99bb2..8235c434 100644 --- a/chrome/chrome_browser_ui.gypi +++ b/chrome/chrome_browser_ui.gypi @@ -406,6 +406,7 @@ 'browser/ui/autofill/password_generation_popup_controller_impl.h', 'browser/ui/autofill/password_generation_popup_controller.h', 'browser/ui/autofill/password_generation_popup_observer.h', + 'browser/ui/autofill/password_generation_popup_view.cc', 'browser/ui/autofill/password_generation_popup_view.h', 'browser/ui/autofill/popup_controller_common.cc', 'browser/ui/autofill/popup_controller_common.h', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 1272abe..39f6e5c 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -187,6 +187,7 @@ 'browser/notifications/desktop_notifications_unittest.cc', 'browser/notifications/desktop_notifications_unittest.h', 'browser/notifications/notification_browsertest.cc', + 'browser/password_manager/password_generation_interactive_uitest.cc', 'browser/task_manager/task_manager_browsertest_util.cc', 'browser/ui/app_list/app_list_service_interactive_uitest.cc', 'browser/ui/app_list/app_list_service_mac_interactive_uitest.mm', @@ -953,6 +954,8 @@ '../components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc', '../components/autofill/content/renderer/test_password_autofill_agent.h', '../components/autofill/content/renderer/test_password_autofill_agent.cc', + '../components/autofill/content/renderer/test_password_generation_agent.h', + '../components/autofill/content/renderer/test_password_generation_agent.cc', 'app/chrome_command_ids.h', 'app/chrome_dll.rc', 'app/chrome_dll_resource.h', @@ -2100,6 +2103,8 @@ 'sources': [ '../components/autofill/content/renderer/test_password_autofill_agent.cc', '../components/autofill/content/renderer/test_password_autofill_agent.h', + '../components/autofill/content/renderer/test_password_generation_agent.cc', + '../components/autofill/content/renderer/test_password_generation_agent.h', 'app/chrome_command_ids.h', 'app/chrome_dll.rc', 'app/chrome_dll_resource.h', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index e8268bf..77f8003 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -527,6 +527,8 @@ '../apps/shell_window_geometry_cache_unittest.cc', '../components/autofill/content/renderer/test_password_autofill_agent.cc', '../components/autofill/content/renderer/test_password_autofill_agent.h', + '../components/autofill/content/renderer/test_password_generation_agent.cc', + '../components/autofill/content/renderer/test_password_generation_agent.h', '../extensions/browser/admin_policy_unittest.cc', '../extensions/browser/error_map_unittest.cc', '../extensions/browser/event_listener_map_unittest.cc', diff --git a/chrome/renderer/autofill/password_generation_agent_browsertest.cc b/chrome/renderer/autofill/password_generation_agent_browsertest.cc index b39c06e..5e24681 100644 --- a/chrome/renderer/autofill/password_generation_agent_browsertest.cc +++ b/chrome/renderer/autofill/password_generation_agent_browsertest.cc @@ -5,11 +5,11 @@ #include <string.h> #include "base/memory/scoped_ptr.h" -#include "base/memory/scoped_vector.h" #include "base/strings/utf_string_conversions.h" #include "chrome/test/base/chrome_render_view_test.h" #include "components/autofill/content/common/autofill_messages.h" -#include "components/autofill/content/renderer/password_generation_agent.h" +#include "components/autofill/content/renderer/autofill_agent.h" +#include "components/autofill/content/renderer/test_password_generation_agent.h" #include "components/autofill/core/common/form_data.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/public/platform/WebString.h" @@ -24,79 +24,21 @@ using blink::WebString; namespace autofill { -class TestPasswordGenerationAgent : public PasswordGenerationAgent { - public: - explicit TestPasswordGenerationAgent(content::RenderView* view) - : PasswordGenerationAgent(view) {} - virtual ~TestPasswordGenerationAgent() {} - - // Make this public - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { - return PasswordGenerationAgent::OnMessageReceived(message); - } - - const std::vector<IPC::Message*>& messages() { - return messages_.get(); - } - - void ClearMessages() { - messages_.clear(); - } - - protected: - virtual bool ShouldAnalyzeDocument(const blink::WebDocument& document) const - OVERRIDE { - return true; - } - - virtual bool Send(IPC::Message* message) OVERRIDE { - messages_.push_back(message); - return true; - } - - private: - ScopedVector<IPC::Message> messages_; - - DISALLOW_COPY_AND_ASSIGN(TestPasswordGenerationAgent); -}; - class PasswordGenerationAgentTest : public ChromeRenderViewTest { public: PasswordGenerationAgentTest() {} - virtual void SetUp() { - // We don't actually create a PasswordGenerationAgent during - // ChromeRenderViewTest::SetUp because it's behind a flag. Since we want - // to use a test manager anyway, we just create our own. - ChromeRenderViewTest::SetUp(); - generation_manager_.reset(new TestPasswordGenerationAgent(view_)); - } - virtual void TearDown() { LoadHTML(""); - generation_manager_.reset(); ChromeRenderViewTest::TearDown(); } - void SimulateClickOnDecoration(blink::WebInputElement* input_element) { - generation_manager_->ClearMessages(); - blink::WebElement decoration = - input_element->decorationElementFor(generation_manager_.get()); - decoration.simulateClick(); - } - - bool DecorationIsVisible(blink::WebInputElement* input_element) { - blink::WebElement decoration = - input_element->decorationElementFor(generation_manager_.get()); - return decoration.hasNonEmptyBoundingBox(); - } - void SetNotBlacklistedMessage(const char* form_str) { autofill::PasswordForm form; form.origin = GURL(base::StringPrintf("data:text/html;charset=utf-8,%s", form_str)); AutofillMsg_FormNotBlacklisted msg(0, form); - generation_manager_->OnMessageReceived(msg); + password_generation_->OnMessageReceived(msg); } void SetAccountCreationFormsDetectedMessage(const char* form_str) { @@ -106,29 +48,29 @@ class PasswordGenerationAgentTest : public ChromeRenderViewTest { std::vector<autofill::FormData> forms; forms.push_back(form); AutofillMsg_AccountCreationFormsDetected msg(0, forms); - generation_manager_->OnMessageReceived(msg); + password_generation_->OnMessageReceived(msg); } - void ExpectPasswordGenerationIconShown(const char* element_id, bool shown) { + void ExpectPasswordGenerationAvailable(const char* element_id, + bool available) { WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(element_id)); ASSERT_FALSE(element.isNull()); WebInputElement target_element = element.to<WebInputElement>(); - if (shown) { - EXPECT_TRUE(DecorationIsVisible(&target_element)); - SimulateClickOnDecoration(&target_element); - EXPECT_EQ(1u, generation_manager_->messages().size()); + ExecuteJavaScript( + base::StringPrintf("document.getElementById('%s').focus();", + element_id).c_str()); + if (available) { + ASSERT_EQ(1u, password_generation_->messages().size()); EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID, - generation_manager_->messages()[0]->type()); + password_generation_->messages()[0]->type()); } else { - EXPECT_FALSE(DecorationIsVisible(&target_element)); + EXPECT_EQ(0u, password_generation_->messages().size()); } + password_generation_->clear_messages(); } - protected: - scoped_ptr<TestPasswordGenerationAgent> generation_manager_; - private: DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTest); }; @@ -169,11 +111,11 @@ const char kInvalidActionAccountCreationFormHTML[] = TEST_F(PasswordGenerationAgentTest, DetectionTest) { // Don't shown the icon for non account creation forms. LoadHTML(kSigninFormHTML); - ExpectPasswordGenerationIconShown("password", false); + ExpectPasswordGenerationAvailable("password", false); // We don't show the decoration yet because the feature isn't enabled. LoadHTML(kAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", false); + ExpectPasswordGenerationAvailable("first_password", false); // Pretend like We have received message indicating site is not blacklisted, // and we have received message indicating the form is classified as @@ -181,20 +123,20 @@ TEST_F(PasswordGenerationAgentTest, DetectionTest) { LoadHTML(kAccountCreationFormHTML); SetNotBlacklistedMessage(kAccountCreationFormHTML); SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", true); + ExpectPasswordGenerationAvailable("first_password", true); // This doesn't trigger because hidden password fields are ignored. LoadHTML(kHiddenPasswordAccountCreationFormHTML); SetNotBlacklistedMessage(kHiddenPasswordAccountCreationFormHTML); SetAccountCreationFormsDetectedMessage( kHiddenPasswordAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", false); + ExpectPasswordGenerationAvailable("first_password", false); // This doesn't trigger because the form action is invalid. LoadHTML(kInvalidActionAccountCreationFormHTML); SetNotBlacklistedMessage(kInvalidActionAccountCreationFormHTML); SetAccountCreationFormsDetectedMessage(kInvalidActionAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", false); + ExpectPasswordGenerationAvailable("first_password", false); } TEST_F(PasswordGenerationAgentTest, FillTest) { @@ -216,7 +158,7 @@ TEST_F(PasswordGenerationAgentTest, FillTest) { base::string16 password = base::ASCIIToUTF16("random_password"); AutofillMsg_GeneratedPasswordAccepted msg(0, password); - generation_manager_->OnMessageReceived(msg); + password_generation_->OnMessageReceived(msg); // Password fields are filled out and set as being autofilled. EXPECT_EQ(password, first_password_element.value()); @@ -232,26 +174,62 @@ TEST_F(PasswordGenerationAgentTest, FillTest) { EXPECT_EQ(element, document.focusedNode()); } +TEST_F(PasswordGenerationAgentTest, EditingTest) { + LoadHTML(kAccountCreationFormHTML); + SetNotBlacklistedMessage(kAccountCreationFormHTML); + SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); + + WebDocument document = GetMainFrame()->document(); + WebElement element = + document.getElementById(WebString::fromUTF8("first_password")); + ASSERT_FALSE(element.isNull()); + WebInputElement first_password_element = element.to<WebInputElement>(); + element = document.getElementById(WebString::fromUTF8("second_password")); + ASSERT_FALSE(element.isNull()); + WebInputElement second_password_element = element.to<WebInputElement>(); + + base::string16 password = base::ASCIIToUTF16("random_password"); + AutofillMsg_GeneratedPasswordAccepted msg(0, password); + password_generation_->OnMessageReceived(msg); + + // Passwords start out the same. + EXPECT_EQ(password, first_password_element.value()); + EXPECT_EQ(password, second_password_element.value()); + + // After editing the first field they are still the same. + base::string16 edited_password = base::ASCIIToUTF16("edited_password"); + first_password_element.setValue(edited_password); + // Cast to WebAutofillClient where textFieldDidChange() is public. + static_cast<blink::WebAutofillClient*>(autofill_agent_)->textFieldDidChange( + first_password_element); + // textFieldDidChange posts a task, so we need to wait until it's been + // processed. + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(edited_password, first_password_element.value()); + EXPECT_EQ(edited_password, second_password_element.value()); +} + TEST_F(PasswordGenerationAgentTest, BlacklistedTest) { // Did not receive not blacklisted message. Don't show password generation // icon. LoadHTML(kAccountCreationFormHTML); SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", false); + ExpectPasswordGenerationAvailable("first_password", false); // Receive one not blacklisted message for non account creation form. Don't // show password generation icon. LoadHTML(kAccountCreationFormHTML); SetNotBlacklistedMessage(kSigninFormHTML); SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", false); + ExpectPasswordGenerationAvailable("first_password", false); // Receive one not blackliste message for account creation form. Show password // generation icon. LoadHTML(kAccountCreationFormHTML); SetNotBlacklistedMessage(kAccountCreationFormHTML); SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", true); + ExpectPasswordGenerationAvailable("first_password", true); // Receive two not blacklisted messages, one is for account creation form and // the other is not. Show password generation icon. @@ -259,7 +237,7 @@ TEST_F(PasswordGenerationAgentTest, BlacklistedTest) { SetNotBlacklistedMessage(kAccountCreationFormHTML); SetNotBlacklistedMessage(kSigninFormHTML); SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", true); + ExpectPasswordGenerationAvailable("first_password", true); } TEST_F(PasswordGenerationAgentTest, AccountCreationFormsDetectedTest) { @@ -267,14 +245,14 @@ TEST_F(PasswordGenerationAgentTest, AccountCreationFormsDetectedTest) { // password generation icon. LoadHTML(kAccountCreationFormHTML); SetNotBlacklistedMessage(kAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", false); + ExpectPasswordGenerationAvailable("first_password", false); // Receive the account creation forms detected message. Show password // generation icon. LoadHTML(kAccountCreationFormHTML); SetNotBlacklistedMessage(kAccountCreationFormHTML); SetAccountCreationFormsDetectedMessage(kAccountCreationFormHTML); - ExpectPasswordGenerationIconShown("first_password", true); + ExpectPasswordGenerationAvailable("first_password", true); } } // namespace autofill diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc index 9ea0b8f..870b7a3 100644 --- a/chrome/renderer/chrome_content_renderer_client.cc +++ b/chrome/renderer/chrome_content_renderer_client.cc @@ -68,7 +68,6 @@ #include "components/autofill/content/renderer/autofill_agent.h" #include "components/autofill/content/renderer/password_autofill_agent.h" #include "components/autofill/content/renderer/password_generation_agent.h" -#include "components/autofill/core/common/password_generation_util.h" #include "components/nacl/renderer/ppb_nacl_private_impl.h" #include "components/plugins/renderer/mobile_youtube_plugin.h" #include "components/visitedlink/renderer/visitedlink_slave.h" @@ -406,13 +405,15 @@ void ChromeContentRendererClient::RenderViewCreated( safe_browsing::MalwareDOMDetails::Create(render_view); #endif + PasswordGenerationAgent* password_generation_agent = + new PasswordGenerationAgent(render_view); PasswordAutofillAgent* password_autofill_agent = new PasswordAutofillAgent(render_view); - new AutofillAgent(render_view, password_autofill_agent); + new AutofillAgent(render_view, + password_autofill_agent, + password_generation_agent); CommandLine* command_line = CommandLine::ForCurrentProcess(); - if (autofill::password_generation::IsPasswordGenerationEnabled()) - new PasswordGenerationAgent(render_view); if (command_line->HasSwitch(switches::kInstantProcess)) new SearchBox(render_view); diff --git a/chrome/test/base/chrome_render_view_test.cc b/chrome/test/base/chrome_render_view_test.cc index 47e06f9..e92236f 100644 --- a/chrome/test/base/chrome_render_view_test.cc +++ b/chrome/test/base/chrome_render_view_test.cc @@ -14,6 +14,7 @@ #include "components/autofill/content/renderer/autofill_agent.h" #include "components/autofill/content/renderer/password_autofill_agent.h" #include "components/autofill/content/renderer/test_password_autofill_agent.h" +#include "components/autofill/content/renderer/test_password_generation_agent.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/common/renderer_preferences.h" #include "content/public/renderer/render_view.h" @@ -40,6 +41,7 @@ using blink::WebString; using blink::WebURLRequest; using autofill::AutofillAgent; using autofill::PasswordAutofillAgent; +using autofill::PasswordGenerationAgent; ChromeRenderViewTest::ChromeRenderViewTest() : extension_dispatcher_(NULL) { } @@ -60,11 +62,13 @@ void ChromeRenderViewTest::SetUp() { content::RenderViewTest::SetUp(); - // RenderView doesn't expose its PasswordAutofillAgent or AutofillAgent - // objects, because it has no need to store them directly (they're stored as - // RenderViewObserver*). So just create another set. + // RenderView doesn't expose its Agent objects, because it has no need to + // store them directly (they're stored as RenderViewObserver*). So just + // create another set. password_autofill_ = new autofill::TestPasswordAutofillAgent(view_); - autofill_agent_ = new AutofillAgent(view_, password_autofill_); + password_generation_ = new autofill::TestPasswordGenerationAgent(view_); + autofill_agent_ = + new AutofillAgent(view_, password_autofill_, password_generation_); } void ChromeRenderViewTest::TearDown() { diff --git a/chrome/test/base/chrome_render_view_test.h b/chrome/test/base/chrome_render_view_test.h index 7e1e6c3..aff4c5d 100644 --- a/chrome/test/base/chrome_render_view_test.h +++ b/chrome/test/base/chrome_render_view_test.h @@ -14,6 +14,7 @@ namespace autofill { class AutofillAgent; class TestPasswordAutofillAgent; +class TestPasswordGenerationAgent; } namespace extensions { @@ -34,6 +35,7 @@ class ChromeRenderViewTest : public content::RenderViewTest { extensions::Dispatcher* extension_dispatcher_; autofill::TestPasswordAutofillAgent* password_autofill_; + autofill::TestPasswordGenerationAgent* password_generation_; autofill::AutofillAgent* autofill_agent_; // Naked pointer as ownership is with content::RenderViewTest::render_thread_. diff --git a/components/autofill/content/common/autofill_messages.h b/components/autofill/content/common/autofill_messages.h index 2e6e960..54fd250 100644 --- a/components/autofill/content/common/autofill_messages.h +++ b/components/autofill/content/common/autofill_messages.h @@ -232,14 +232,22 @@ IPC_MESSAGE_ROUTED0(AutofillHostMsg_DidEndTextFieldEditing) // Instructs the browser to hide the Autofill UI. IPC_MESSAGE_ROUTED0(AutofillHostMsg_HideAutofillUI) -// Instructs the browser to show the password generation bubble at the +// Instructs the browser to show the password generation popup at the // specified location. This location should be specified in the renderers // coordinate system. Form is the form associated with the password field. IPC_MESSAGE_ROUTED3(AutofillHostMsg_ShowPasswordGenerationPopup, - gfx::Rect /* source location */, + gfx::RectF /* source location */, int /* max length of the password */, autofill::PasswordForm) +// Instructs the browser to show the popup for editing a generated password. +// The location should be specified in the renderers coordinate system. +IPC_MESSAGE_ROUTED1(AutofillHostMsg_ShowPasswordEditingPopup, + gfx::RectF /* source location */) + +// Instructs the browser to hide any password generation popups. +IPC_MESSAGE_ROUTED0(AutofillHostMsg_HidePasswordGenerationPopup) + // Instruct the browser that a password mapping has been found for a field. IPC_MESSAGE_ROUTED2(AutofillHostMsg_AddPasswordFormMapping, autofill::FormFieldData, /* the user name field */ diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc index 1b37ce2..979976f 100644 --- a/components/autofill/content/renderer/autofill_agent.cc +++ b/components/autofill/content/renderer/autofill_agent.cc @@ -15,6 +15,7 @@ #include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/page_click_tracker.h" #include "components/autofill/content/renderer/password_autofill_agent.h" +#include "components/autofill/content/renderer/password_generation_agent.h" #include "components/autofill/core/common/autofill_constants.h" #include "components/autofill/core/common/autofill_data_validation.h" #include "components/autofill/core/common/autofill_switches.h" @@ -109,20 +110,14 @@ void TrimStringVectorForIPC(std::vector<base::string16>* strings) { } } -gfx::RectF GetScaledBoundingBox(float scale, WebInputElement* element) { - gfx::Rect bounding_box(element->boundsInViewportSpace()); - return gfx::RectF(bounding_box.x() * scale, - bounding_box.y() * scale, - bounding_box.width() * scale, - bounding_box.height() * scale); -} - } // namespace AutofillAgent::AutofillAgent(content::RenderView* render_view, - PasswordAutofillAgent* password_autofill_agent) + PasswordAutofillAgent* password_autofill_agent, + PasswordGenerationAgent* password_generation_agent) : content::RenderViewObserver(render_view), password_autofill_agent_(password_autofill_agent), + password_generation_agent_(password_generation_agent), autofill_query_id_(0), autofill_action_(AUTOFILL_NONE), web_view_(render_view->GetWebView()), @@ -334,6 +329,11 @@ void AutofillAgent::TextFieldDidChangeImpl(const WebInputElement& element) { if (!element.focused()) return; + if (password_generation_agent_ && + password_generation_agent_->TextDidChangeInTextField(element)) { + return; + } + if (password_autofill_agent_->TextDidChangeInTextField(element)) { element_ = element; return; diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h index 7bc7f60..cb4df03 100644 --- a/components/autofill/content/renderer/autofill_agent.h +++ b/components/autofill/content/renderer/autofill_agent.h @@ -32,6 +32,7 @@ struct FormData; struct FormFieldData; struct WebElementDescriptor; class PasswordAutofillAgent; +class PasswordGenerationAgent; // AutofillAgent deals with Autofill related communications between WebKit and // the browser. There is one AutofillAgent per RenderView. @@ -46,8 +47,11 @@ class AutofillAgent : public content::RenderViewObserver, public blink::WebAutofillClient { public: // PasswordAutofillAgent is guaranteed to outlive AutofillAgent. + // PasswordGenerationAgent may be NULL. If it is not, then it is also + // guaranteed to outlive AutofillAgent. AutofillAgent(content::RenderView* render_view, - PasswordAutofillAgent* password_autofill_manager); + PasswordAutofillAgent* password_autofill_manager, + PasswordGenerationAgent* password_generation_agent); virtual ~AutofillAgent(); private: @@ -174,7 +178,8 @@ class AutofillAgent : public content::RenderViewObserver, FormCache form_cache_; - PasswordAutofillAgent* password_autofill_agent_; // WEAK reference. + PasswordAutofillAgent* password_autofill_agent_; // Weak reference. + PasswordGenerationAgent* password_generation_agent_; // Weak reference. // The ID of the last request sent for form field Autofill. Used to ignore // out of date responses. diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc index 74d5b68..5d1f379 100644 --- a/components/autofill/content/renderer/form_autofill_util.cc +++ b/components/autofill/content/renderer/form_autofill_util.cc @@ -1175,4 +1175,12 @@ bool IsWebElementEmpty(const blink::WebElement& element) { return true; } +gfx::RectF GetScaledBoundingBox(float scale, WebInputElement* element) { + gfx::Rect bounding_box(element->boundsInViewportSpace()); + return gfx::RectF(bounding_box.x() * scale, + bounding_box.y() * scale, + bounding_box.width() * scale, + bounding_box.height() * scale); +} + } // namespace autofill diff --git a/components/autofill/content/renderer/form_autofill_util.h b/components/autofill/content/renderer/form_autofill_util.h index 3913481..f3384f1 100644 --- a/components/autofill/content/renderer/form_autofill_util.h +++ b/components/autofill/content/renderer/form_autofill_util.h @@ -8,6 +8,7 @@ #include <vector> #include "base/strings/string16.h" +#include "ui/gfx/rect.h" namespace blink { class WebDocument; @@ -167,6 +168,9 @@ bool IsWebpageEmpty(const blink::WebFrame* frame); // are of the type <script>, <meta>, or <title>. bool IsWebElementEmpty(const blink::WebElement& element); +// Return a gfx::RectF that is the bounding box for |element| scaled by |scale|. +gfx::RectF GetScaledBoundingBox(float scale, blink::WebInputElement* element); + } // namespace autofill #endif // COMPONENTS_AUTOFILL_CONTENT_RENDERER_FORM_AUTOFILL_UTIL_H_ diff --git a/components/autofill/content/renderer/password_generation_agent.cc b/components/autofill/content/renderer/password_generation_agent.cc index da9b863..f7e01d1 100644 --- a/components/autofill/content/renderer/password_generation_agent.cc +++ b/components/autofill/content/renderer/password_generation_agent.cc @@ -8,6 +8,7 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "components/autofill/content/common/autofill_messages.h" +#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/password_form_conversion_utils.h" #include "components/autofill/core/common/autofill_switches.h" #include "components/autofill/core/common/form_data.h" @@ -98,7 +99,11 @@ bool ContainsForm(const std::vector<autofill::FormData>& forms, PasswordGenerationAgent::PasswordGenerationAgent( content::RenderView* render_view) : content::RenderViewObserver(render_view), - render_view_(render_view) { + render_view_(render_view), + password_is_generated_(false), + password_edited_(false), + enabled_(password_generation::IsPasswordGenerationEnabled()) { + DVLOG(2) << "Password Generation is " << (enabled_ ? "Enabled" : "Disabled"); render_view_->GetWebView()->setPasswordGeneratorClient(this); } PasswordGenerationAgent::~PasswordGenerationAgent() {} @@ -108,21 +113,28 @@ void PasswordGenerationAgent::DidFinishDocumentLoad(blink::WebFrame* frame) { // to query whether the current form is blacklisted or not happens when the // document load finishes, so we need to clear previous states here before we // hear back from the browser. We only clear this state on main frame load - // as we don't want subframe loads to clear state that we have recieved from + // as we don't want subframe loads to clear state that we have received from // the main frame. Note that we assume there is only one account creation // form, but there could be multiple password forms in each frame. - // - // TODO(zysxqn): Add stat when local heuristic fires but we don't show the - // password generation icon. if (!frame->parent()) { not_blacklisted_password_form_origins_.clear(); generation_enabled_forms_.clear(); + generation_element_.reset(); possible_account_creation_form_.reset(new PasswordForm()); - passwords_.clear(); + password_elements_.clear(); + password_is_generated_ = false; + if (password_edited_) { + password_generation::LogPasswordGenerationEvent( + password_generation::PASSWORD_EDITED); + } + password_edited_ = false; } } void PasswordGenerationAgent::DidFinishLoad(blink::WebFrame* frame) { + if (!enabled_) + return; + // We don't want to generate passwords if the browser won't store or sync // them. if (!ShouldAnalyzeDocument(frame->document())) @@ -154,9 +166,9 @@ void PasswordGenerationAgent::DidFinishLoad(blink::WebFrame* frame) { DVLOG(2) << "Account creation form detected"; password_generation::LogPasswordGenerationEvent( password_generation::SIGN_UP_DETECTED); - passwords_ = passwords; + password_elements_ = passwords; possible_account_creation_form_.swap(password_form); - MaybeShowIcon(); + DetermineGenerationElement(); // We assume that there is only one account creation field per URL. return; } @@ -191,8 +203,6 @@ void PasswordGenerationAgent::openPasswordGenerator( rect, element.maxLength(), *password_form)); - password_generation::LogPasswordGenerationEvent( - password_generation::BUBBLE_SHOWN); } bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message& message) { @@ -211,13 +221,17 @@ bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message& message) { void PasswordGenerationAgent::OnFormNotBlacklisted(const PasswordForm& form) { not_blacklisted_password_form_origins_.push_back(form.origin); - MaybeShowIcon(); + DetermineGenerationElement(); } void PasswordGenerationAgent::OnPasswordAccepted( const base::string16& password) { - for (std::vector<blink::WebInputElement>::iterator it = passwords_.begin(); - it != passwords_.end(); ++it) { + password_is_generated_ = true; + password_generation::LogPasswordGenerationEvent( + password_generation::PASSWORD_ACCEPTED); + for (std::vector<blink::WebInputElement>::iterator it = + password_elements_.begin(); + it != password_elements_.end(); ++it) { it->setValue(password); it->setAutofilled(true); // Advance focus to the next input field. We assume password fields in @@ -230,45 +244,130 @@ void PasswordGenerationAgent::OnAccountCreationFormsDetected( const std::vector<autofill::FormData>& forms) { generation_enabled_forms_.insert( generation_enabled_forms_.end(), forms.begin(), forms.end()); - MaybeShowIcon(); + DetermineGenerationElement(); } -void PasswordGenerationAgent::MaybeShowIcon() { +void PasswordGenerationAgent::DetermineGenerationElement() { // Make sure local heuristics have identified a possible account creation // form. - if (!possible_account_creation_form_.get() || passwords_.empty()) { + if (!possible_account_creation_form_.get() || password_elements_.empty()) { DVLOG(2) << "Local hueristics have not detected a possible account " << "creation form"; return; } - // Verify that it's not blacklisted. - if (not_blacklisted_password_form_origins_.empty() || - !ContainsURL(not_blacklisted_password_form_origins_, - possible_account_creation_form_->origin)) { - DVLOG(2) << "Have not recieved confirmation that password form isn't " + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kLocalHeuristicsOnlyForPasswordGeneration)) { + DVLOG(2) << "Bypassing additional checks."; + } else if (not_blacklisted_password_form_origins_.empty() || + !ContainsURL(not_blacklisted_password_form_origins_, + possible_account_creation_form_->origin)) { + DVLOG(2) << "Have not received confirmation that password form isn't " << "blacklisted"; return; + } else if (generation_enabled_forms_.empty() || + !ContainsForm(generation_enabled_forms_, + *possible_account_creation_form_)) { + // Note that this message will never be sent if this feature is disabled + // (e.g. Password saving is disabled). + DVLOG(2) << "Have not received confirmation from Autofill that form is " + << "used for account creation"; + return; } - // Ensure that we get a ping from Autofill saying that this form is used for - // account creation. Note that this message will not be set if this feature - // is not enabled. If kNoAutofillNecessaryForPasswordGeneration is set, - // skip this check. This switch should only be used in testing environments. - if (!CommandLine::ForCurrentProcess()->HasSwitch( - switches::kNoAutofillNecessaryForPasswordGeneration) && - (generation_enabled_forms_.empty() || - !ContainsForm(generation_enabled_forms_, - *possible_account_creation_form_))) { - DVLOG(2) << "Have not recieved confirmation from Autofill that form is used" - << " for account creation"; + DVLOG(2) << "Password generation eligible form found"; + generation_element_ = password_elements_[0]; + password_generation::LogPasswordGenerationEvent( + password_generation::GENERATION_AVAILABLE); +} + +void PasswordGenerationAgent::FocusedNodeChanged(const blink::WebNode& node) { + // TODO(gcasto): Re-hide generation_element text. + if (node.isNull() || !node.isElementNode()) return; + + const blink::WebElement web_element = node.toConst<blink::WebElement>(); + if (!web_element.document().frame()) + return; + + const blink::WebInputElement* element = toWebInputElement(&web_element); + if (!element || *element != generation_element_) + return; + + if (password_is_generated_) { + // TODO(gcasto): Make characters visible. + ShowEditingPopup(); + } + + // Only trigger if the password field is empty. + if (!element->isReadOnly() && + element->isEnabled() && + element->value().isEmpty()) { + ShowGenerationPopup(); + } +} + +bool PasswordGenerationAgent::TextDidChangeInTextField( + const blink::WebInputElement& element) { + if (element != generation_element_) + return false; + + if (element.value().isEmpty()) { + if (password_is_generated_) { + // User generated a password and then deleted it. + password_generation::LogPasswordGenerationEvent( + password_generation::PASSWORD_DELETED); + } + + // TODO(gcasto): Set PasswordForm::type in the browser to TYPE_NORMAL. + password_is_generated_ = false; + // Offer generation again. + ShowGenerationPopup(); + } else if (!password_is_generated_) { + // User has rejected the feature and has started typing a password. + HidePopup(); + } else { + password_edited_ = true; + // Mirror edits to any confirmation password fields. + for (std::vector<blink::WebInputElement>::iterator it = + password_elements_.begin(); + it != password_elements_.end(); ++it) { + it->setValue(element.value()); + } } - passwords_[0].passwordGeneratorButtonElement().setAttribute("style", - "display:block"); + return true; +} + +void PasswordGenerationAgent::ShowGenerationPopup() { + gfx::RectF bounding_box_scaled = + GetScaledBoundingBox(render_view_->GetWebView()->pageScaleFactor(), + &generation_element_); + + Send(new AutofillHostMsg_ShowPasswordGenerationPopup( + routing_id(), + bounding_box_scaled, + generation_element_.maxLength(), + *possible_account_creation_form_)); + password_generation::LogPasswordGenerationEvent( - password_generation::ICON_SHOWN); + password_generation::GENERATION_POPUP_SHOWN); +} + +void PasswordGenerationAgent::ShowEditingPopup() { + gfx::RectF bounding_box_scaled = + GetScaledBoundingBox(render_view_->GetWebView()->pageScaleFactor(), + &generation_element_); + + Send(new AutofillHostMsg_ShowPasswordEditingPopup(routing_id(), + bounding_box_scaled)); + + password_generation::LogPasswordGenerationEvent( + password_generation::EDITING_POPUP_SHOWN); +} + +void PasswordGenerationAgent::HidePopup() { + Send(new AutofillHostMsg_HidePasswordGenerationPopup(routing_id())); } } // namespace autofill diff --git a/components/autofill/content/renderer/password_generation_agent.h b/components/autofill/content/renderer/password_generation_agent.h index b632902..dcb0b19 100644 --- a/components/autofill/content/renderer/password_generation_agent.h +++ b/components/autofill/content/renderer/password_generation_agent.h @@ -34,6 +34,10 @@ class PasswordGenerationAgent : public content::RenderViewObserver, explicit PasswordGenerationAgent(content::RenderView* render_view); virtual ~PasswordGenerationAgent(); + // Returns true if the field being changed is one where a generated password + // is being offered. Updates the state of the popup if necessary. + bool TextDidChangeInTextField(const blink::WebInputElement& element); + protected: // Returns true if this document is one that we should consider analyzing. // Virtual so that it can be overriden during testing. @@ -42,10 +46,14 @@ class PasswordGenerationAgent : public content::RenderViewObserver, // RenderViewObserver: virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + // Use to force enable during testing. + void set_enabled(bool enabled) { enabled_ = enabled; } + private: // RenderViewObserver: virtual void DidFinishDocumentLoad(blink::WebFrame* frame) OVERRIDE; virtual void DidFinishLoad(blink::WebFrame* frame) OVERRIDE; + virtual void FocusedNodeChanged(const blink::WebNode& node) OVERRIDE; // WebPasswordGeneratorClient: virtual void openPasswordGenerator(blink::WebInputElement& element) OVERRIDE; @@ -56,8 +64,19 @@ class PasswordGenerationAgent : public content::RenderViewObserver, void OnAccountCreationFormsDetected( const std::vector<autofill::FormData>& forms); - // Helper function to decide whether we should show password generation icon. - void MaybeShowIcon(); + // Helper function to decide if |passwords_| contains password fields for + // an account creation form. Sets |generation_element_| to the field that + // we want to trigger the generation UI on. + void DetermineGenerationElement(); + + // Show password generation UI anchored at |generation_element_|. + void ShowGenerationPopup(); + + // Show UI for editing a generated password at |generation_element_|. + void ShowEditingPopup(); + + // Hides a password generation popup if one exists. + void HidePopup(); content::RenderView* render_view_; @@ -74,7 +93,22 @@ class PasswordGenerationAgent : public content::RenderViewObserver, // not be sent if the feature is disabled. std::vector<autofill::FormData> generation_enabled_forms_; - std::vector<blink::WebInputElement> passwords_; + // Password elements that may be part of an account creation form. + std::vector<blink::WebInputElement> password_elements_; + + // Element where we want to trigger password generation UI. + blink::WebInputElement generation_element_; + + // If the password field at |generation_element_| contains a generated + // password. + bool password_is_generated_; + + // True if a password was generated and the user edited it. Used for UMA + // stats. + bool password_edited_; + + // If this feature is enabled. Controlled by Finch. + bool enabled_; DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgent); }; diff --git a/components/autofill/content/renderer/test_password_generation_agent.cc b/components/autofill/content/renderer/test_password_generation_agent.cc new file mode 100644 index 0000000..2e4adb9 --- /dev/null +++ b/components/autofill/content/renderer/test_password_generation_agent.cc @@ -0,0 +1,33 @@ +// 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 "components/autofill/content/renderer/test_password_generation_agent.h" + +namespace autofill { + +TestPasswordGenerationAgent::TestPasswordGenerationAgent( + content::RenderView* render_view) + : PasswordGenerationAgent(render_view) { + // Always enable when testing. + set_enabled(true); +} + +TestPasswordGenerationAgent::~TestPasswordGenerationAgent() {} + +bool TestPasswordGenerationAgent::OnMessageReceived( + const IPC::Message& message) { + return PasswordGenerationAgent::OnMessageReceived(message); +} + +bool TestPasswordGenerationAgent::ShouldAnalyzeDocument( + const blink::WebDocument& document) const { + return true; +} + +bool TestPasswordGenerationAgent::Send(IPC::Message* message) { + messages_.push_back(message); + return true; +} + +} // namespace autofill diff --git a/components/autofill/content/renderer/test_password_generation_agent.h b/components/autofill/content/renderer/test_password_generation_agent.h new file mode 100644 index 0000000..e711ab0 --- /dev/null +++ b/components/autofill/content/renderer/test_password_generation_agent.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef COMPONENTS_AUTOFILL_CONTENT_RENDERER_TEST_PASSWORD_GENERATION_AGENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_TEST_PASSWORD_GENERATION_AGENT_H_ + +#include <vector> + +#include "base/memory/scoped_vector.h" +#include "components/autofill/content/renderer/password_generation_agent.h" +#include "ipc/ipc_message.h" + +namespace autofill { + +class TestPasswordGenerationAgent : public PasswordGenerationAgent { + public: + explicit TestPasswordGenerationAgent(content::RenderView* render_view); + virtual ~TestPasswordGenerationAgent(); + + // content::RenderViewObserver implementation: + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual bool Send(IPC::Message* message) OVERRIDE; + + // Access messages that would have been sent to the browser. + const std::vector<IPC::Message*>& messages() const { return messages_.get(); } + + // Clear outgoing message queue. + void clear_messages() { messages_.clear(); } + + // PasswordGenreationAgent implementation: + // Always return true to allow loading of data URLs. + virtual bool ShouldAnalyzeDocument( + const blink::WebDocument& document) const OVERRIDE; + + private: + ScopedVector<IPC::Message> messages_; + + DISALLOW_COPY_AND_ASSIGN(TestPasswordGenerationAgent); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_RENDERER_TEST_PASSWORD_AUTOFILL_AGENT_H_ diff --git a/components/autofill/core/common/autofill_switches.cc b/components/autofill/core/common/autofill_switches.cc index 8174c03..c7cd3acd 100644 --- a/components/autofill/core/common/autofill_switches.cc +++ b/components/autofill/core/common/autofill_switches.cc @@ -31,10 +31,10 @@ const char kEnableInteractiveAutocomplete[] = "enable-interactive-autocomplete"; // account creation. const char kEnablePasswordGeneration[] = "enable-password-generation"; -// Removes the requirement that we recieved a ping from the autofill servers. -// Used in testing. -const char kNoAutofillNecessaryForPasswordGeneration[] = - "no-autofill-for-password-generation"; +// Removes the requirement that we recieved a ping from the autofill servers +// and that the user doesn't have the given form blacklisted. Used in testing. +const char kLocalHeuristicsOnlyForPasswordGeneration[] = + "local-heuristics-only-for-password-generation"; // Annotates forms with Autofill field type predictions. const char kShowAutofillTypePredictions[] = "show-autofill-type-predictions"; diff --git a/components/autofill/core/common/autofill_switches.h b/components/autofill/core/common/autofill_switches.h index 21c3f6d..ec8c6bb 100644 --- a/components/autofill/core/common/autofill_switches.h +++ b/components/autofill/core/common/autofill_switches.h @@ -16,7 +16,7 @@ extern const char kDisablePasswordGeneration[]; extern const char kEnableIgnoreAutocompleteOff[]; extern const char kEnableInteractiveAutocomplete[]; extern const char kEnablePasswordGeneration[]; -extern const char kNoAutofillNecessaryForPasswordGeneration[]; +extern const char kLocalHeuristicsOnlyForPasswordGeneration[]; extern const char kShowAutofillTypePredictions[]; extern const char kWalletSecureServiceUrl[]; extern const char kWalletServiceUrl[]; diff --git a/components/autofill/core/common/password_generation_util.h b/components/autofill/core/common/password_generation_util.h index 69003c7..daf3c20 100644 --- a/components/autofill/core/common/password_generation_util.h +++ b/components/autofill/core/common/password_generation_util.h @@ -9,6 +9,7 @@ namespace autofill { namespace password_generation { // Enumerates various events related to the password generation process. +// Do not remove items from this enum as they are used for UMA stats logging. enum PasswordGenerationEvent { // No Account creation form is detected. NO_SIGN_UP_DETECTED, @@ -16,11 +17,31 @@ enum PasswordGenerationEvent { // Account creation form is detected. SIGN_UP_DETECTED, - // Password generation icon is shown inside the first password field. - ICON_SHOWN, + // DEPRECATED: Password generation icon shown (old UI). + DEPRECATED_ICON_SHOWN, - // Password generation bubble is shown after user clicks on the icon. - BUBBLE_SHOWN, + // DEPRECATED: Password generation bubble shown (old UI). + DEPRECATED_BUBBLE_SHOWN, + + // Password generation could be triggered if the user selects the appropriate + // element. + GENERATION_AVAILABLE, + + // Password generation popup is shown after user focuses the appropriate + // password field. + GENERATION_POPUP_SHOWN, + + // Generated password was accepted by the user. + PASSWORD_ACCEPTED, + + // User focused the password field containing the generated password. + EDITING_POPUP_SHOWN, + + // Password was changed after generation. + PASSWORD_EDITED, + + // Generated password was deleted by the user + PASSWORD_DELETED, // Number of enum entries, used for UMA histogram reporting macros. EVENT_ENUM_COUNT diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index e4515c0..85f75ac 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -12587,6 +12587,19 @@ other types of suffix sets. </summary> </histogram> +<histogram name="PasswordGeneration.Event" enum="PasswordGenerationEvent"> + <summary> + Measures the frequency of various password generation events. + + Note that this histogram is logged from the renderer process, and + consequently the numbers should not be directly compared to the other + PasswordGeneration.* histograms, which are logged from the browser process. + Histograms logged in different processes are lost at different rates, which + introduces systematic bias between histograms logged in the renderer process + vs. those logged in the browser process. + </summary> +</histogram> + <histogram name="PasswordGeneration.UploadStarted" enum="Boolean"> <summary> The number of times that we try to upload a form that we believe should @@ -28816,6 +28829,19 @@ other types of suffix sets. <int value="3" label="Cookie contains both control chars and is invalid"/> </enum> +<enum name="PasswordGenerationEvent" type="int"> + <int value="0" label="No sign up form"/> + <int value="1" label="Local heuristics found sign up form"/> + <int value="2" label="DEPRECATED: Icon shown"/> + <int value="3" label="DEPRECATED: Bubble shown"/> + <int value="4" label="Generation available"/> + <int value="5" label="Generation popup shown"/> + <int value="6" label="Generated password accepted"/> + <int value="7" label="Editing popup shown"/> + <int value="8" label="Generated password edited"/> + <int value="9" label="Generated password deleted"/> +</enum> + <enum name="PasswordManagerActionsTaken" type="int"> <obsolete> Deprecated as of Chrome 32. See PasswordManagerActionsTakenWithPsl |