diff options
author | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-14 00:19:48 +0000 |
---|---|---|
committer | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-14 00:19:48 +0000 |
commit | 5c2ac85b5f50766b3fe31af931f1b2500e463bfd (patch) | |
tree | 147f50bef6f7fd32850d3df06280dc974ae72065 /webkit/glue | |
parent | e3a3f854c2d09b87cf1373eeaaf26cdb6decb08b (diff) | |
download | chromium_src-5c2ac85b5f50766b3fe31af931f1b2500e463bfd.zip chromium_src-5c2ac85b5f50766b3fe31af931f1b2500e463bfd.tar.gz chromium_src-5c2ac85b5f50766b3fe31af931f1b2500e463bfd.tar.bz2 |
Add autofill dropdown support for password forms.
BUG=5406
Review URL: http://codereview.chromium.org/155399
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20585 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/glue')
-rw-r--r-- | webkit/glue/autocomplete_input_listener.h | 198 | ||||
-rw-r--r-- | webkit/glue/editor_client_impl.cc | 27 | ||||
-rw-r--r-- | webkit/glue/editor_client_impl.h | 9 | ||||
-rw-r--r-- | webkit/glue/password_autocomplete_listener.cc | 46 | ||||
-rw-r--r-- | webkit/glue/password_autocomplete_listener.h | 11 | ||||
-rw-r--r-- | webkit/glue/password_autocomplete_listener_unittest.cc | 14 | ||||
-rw-r--r-- | webkit/glue/webview_impl.cc | 5 |
7 files changed, 93 insertions, 217 deletions
diff --git a/webkit/glue/autocomplete_input_listener.h b/webkit/glue/autocomplete_input_listener.h deleted file mode 100644 index 5f90e98..0000000 --- a/webkit/glue/autocomplete_input_listener.h +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) 2006-2008 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. -// -// This file defines some infrastructure to handle inline autocomplete of DOM -// input elements. - -#ifndef WEBKIT_GLUE_AUTOCOMPLETE_INPUT_LISTENER_H__ -#define WEBKIT_GLUE_AUTOCOMPLETE_INPUT_LISTENER_H__ - -#include "config.h" -#include <map> -#include <string> - -#include "base/compiler_specific.h" - -MSVC_PUSH_WARNING_LEVEL(0); -#include "HTMLBodyElement.h" -#include "EventListener.h" -#include "HTMLInputElement.h" -MSVC_POP_WARNING(); - -#include "base/basictypes.h" - -namespace WebCore { -class AtomicString; -class Event; -} - -namespace webkit_glue { - -// A proxy interface to a WebCore::HTMLInputElement for inline autocomplete. -// This class is NOT used directly by the AutocompleteInputListener but -// is included here since it is likely most listener implementations will -// want to interface with an HTMLInputElement (see PasswordACListener). -// The delegate does not own the WebCore element; it only interfaces it. -class HTMLInputDelegate { - public: - explicit HTMLInputDelegate(WebCore::HTMLInputElement* element); - virtual ~HTMLInputDelegate(); - - virtual void SetValue(const std::wstring& value); - virtual void SetSelectionRange(size_t start, size_t end); - virtual void OnFinishedAutocompleting(); - - private: - // The underlying DOM element we're wrapping. We reference the - // underlying HTMLInputElement for its lifetime to ensure it does not get - // freed by WebCore while in use by the delegate instance. - WebCore::HTMLInputElement* element_; - - DISALLOW_COPY_AND_ASSIGN(HTMLInputDelegate); -}; - - -class AutocompleteInputListener { - public: - virtual ~AutocompleteInputListener() { } - - // OnBlur: DOMFocusOutEvent occured, means one of two things. - // 1. The user removed focus from the text field - // either by tabbing out or clicking; - // 2. The page is being destroyed (e.g user closed the tab) - virtual void OnBlur(WebCore::HTMLInputElement* input_element, - const std::wstring& user_input) = 0; - - // This method is called when there was a user-initiated text delta in - // the edit field that now needs some inline autocompletion. - // ShouldInlineAutocomplete gives the precondition for invoking this method. - virtual void OnInlineAutocompleteNeeded( - WebCore::HTMLInputElement* input_element, - const std::wstring& user_input) = 0; -}; - -// The AutocompleteBodyListener class is a listener on the body element of a -// page that is responsible for reporting blur (for tab/click-out) and input -// events for form elements (registered by calling AddInputListener()). -// This allows to have one global listener for a page, as opposed to registering -// a listener for each form element, which impacts performances. -// Reasons for attaching to DOM directly rather than using EditorClient API: -// 1. Since we don't need to stop listening until the DOM node is unloaded, -// it makes sense to use an object owned by the DOM node itself. Attaching -// as a listener gives you this for free (nodes cleanup their listeners -// upon destruction). -// 2. It allows fine-grained control when the popup/down is implemented -// in handling key events / selecting elements. -class AutocompleteBodyListener : public WebCore::EventListener { - public: - // Constructs a listener for the specified frame. It listens for blur and - // input events for elements that are registered through the AddListener - // method. - // The listener is ref counted (because it inherits from - // WebCore::EventListener). - explicit AutocompleteBodyListener(WebCore::Frame* frame); - virtual ~AutocompleteBodyListener(); - - // Used by unit-tests. - AutocompleteBodyListener() { } - - // Adds a listener for the specified |element|. - // This call takes ownership of the |listener|. Note that it is still OK to - // add the same listener for different elements. - void AddInputListener(WebCore::HTMLInputElement* element, - AutocompleteInputListener* listener); - - // EventListener implementation. Code that is common to inline autocomplete, - // such as deciding whether or not it is safe to perform it, is refactored - // into this method and the appropriate delegate method is invoked. - virtual void handleEvent(WebCore::Event* event, bool is_window_event); - - protected: - // Protected for mocking purposes. - virtual bool IsCaretAtEndOfText(WebCore::HTMLInputElement* element, - size_t input_length, - size_t previous_length) const; - - private: - // Determines, based on current state (previous_text_) and user input, - // whether or not it is a good idea to attempt inline autocomplete. - // - // This method is based on firefox2 code in - // toolkit/components/autocomplete/src/nsAutoCompleteController.cpp - // Its license block is: - /* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Joe Hewitt <hewitt@netscape.com> (Original Author) - * Dean Tessman <dean_tessman@hotmail.com> - * Johnny Stenback <jst@mozilla.jstenback.com> - * Masayuki Nakano <masayuki@d-toybox.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - // The semantics of deciding whether or not the field is in a inline- - // autocomplete-healthy state are summarized: - // 1. The text is not identical to the text on the previous input event. - // 2. This is not the result of a backspace. - // 3. The text is not empty. - // 4. The caret is at the end of the textbox. - // TODO(timsteele): Examine autocomplete_edit.cc in the browser/ code and - // make sure to capture all common exclusion cases here. - bool ShouldInlineAutocomplete(WebCore::HTMLInputElement* input, - const std::wstring& old_text, - const std::wstring& new_text); - - // The data we store for each registered listener. - struct InputElementInfo { - InputElementInfo() : listener(NULL) { } - AutocompleteInputListener* listener; - std::wstring previous_text; - }; - - struct CmpRefPtrs { - bool operator()(const RefPtr<WebCore::HTMLInputElement>& a, - const RefPtr<WebCore::HTMLInputElement>& b) const { - return a.get() < b.get(); - } - }; - - typedef std::map<RefPtr<WebCore::HTMLInputElement>, InputElementInfo, - CmpRefPtrs> InputElementInfoMap; - InputElementInfoMap elements_info_; - - DISALLOW_COPY_AND_ASSIGN(AutocompleteBodyListener); -}; - -} // webkit_glue - -#endif // WEBKIT_GLUE_AUTOCOMPLETE_INPUT_LISTENER_H__ diff --git a/webkit/glue/editor_client_impl.cc b/webkit/glue/editor_client_impl.cc index 07d1b5c..5094545 100644 --- a/webkit/glue/editor_client_impl.cc +++ b/webkit/glue/editor_client_impl.cc @@ -73,7 +73,7 @@ EditorClientImpl::EditorClientImpl(WebView* web_view) : web_view_(static_cast<WebViewImpl*>(web_view)), use_editor_delegate_(false), in_redo_(false), - backspace_pressed_(false), + backspace_or_delete_pressed_(false), spell_check_this_field_status_(SPELLCHECK_AUTOMATIC), // Don't complain about using "this" in initializer list. MSVC_PUSH_DISABLE_WARNING(4355) @@ -749,7 +749,7 @@ bool EditorClientImpl::Autofill(WebCore::HTMLInputElement* input_element, if (!requires_caret_at_end) { DoAutofill(input_element, form_autofill_only, autofill_on_empty_value, - false, backspace_pressed_); + false, backspace_or_delete_pressed_); } else { // We post a task for doing the autofill as the caret position is not set // properly at this point (http://bugs.webkit.org/show_bug.cgi?id=16976) and @@ -763,7 +763,7 @@ bool EditorClientImpl::Autofill(WebCore::HTMLInputElement* input_element, form_autofill_only, autofill_on_empty_value, true, - backspace_pressed_)); + backspace_or_delete_pressed_)); } return true; } @@ -797,10 +797,7 @@ void EditorClientImpl::DoAutofill(WebCore::HTMLInputElement* input_element, if (form_autofill_only) return; - if (backspace) // No autocomplete for password on backspace. - return; - - listener->OnInlineAutocompleteNeeded(input_element, value); + listener->OnInlineAutocompleteNeeded(input_element, value, backspace, true); return; } @@ -811,6 +808,19 @@ void EditorClientImpl::DoAutofill(WebCore::HTMLInputElement* input_element, reinterpret_cast<int64>(input_element)); } +void EditorClientImpl::OnAutofillSuggestionAccepted( + WebCore::HTMLInputElement* text_field) { + WebFrameImpl* webframe = + WebFrameImpl::FromFrame(text_field->document()->frame()); + webkit_glue::PasswordAutocompleteListener* listener = + webframe->GetPasswordListener(text_field); + std::wstring value = webkit_glue::StringToStdWString(text_field->value()); + // Password listeners need to autocomplete other fields that depend on the + // input element with autofill suggestions. + if (listener) + listener->OnInlineAutocompleteNeeded(text_field, value, false, false); +} + bool EditorClientImpl::doTextFieldCommandFromEvent( WebCore::Element* element, WebCore::KeyboardEvent* event) { @@ -818,7 +828,8 @@ bool EditorClientImpl::doTextFieldCommandFromEvent( // find if backspace was pressed from textFieldDidBeginEditing and // textDidChangeInTextField as when these methods are called the value of the // input element already contains the type character. - backspace_pressed_ = (event->keyCode() == WebCore::VKEY_BACK); + backspace_or_delete_pressed_ = (event->keyCode() == WebCore::VKEY_BACK) || + (event->keyCode() == WebCore::VKEY_DELETE); // The Mac code appears to use this method as a hook to implement special // keyboard commands specific to Safari's auto-fill implementation. We diff --git a/webkit/glue/editor_client_impl.h b/webkit/glue/editor_client_impl.h index 4bacad2a..ad133a7 100644 --- a/webkit/glue/editor_client_impl.h +++ b/webkit/glue/editor_client_impl.h @@ -124,6 +124,13 @@ class EditorClientImpl : public WebCore::EditorClient { // otherwise. virtual bool ShowFormAutofillForNode(WebCore::Node* node); + // Notification that the text changed in |text_field| due to acceptance of + // a suggestion provided by an autofill popup. Having a separate callback + // in this case is a simple way to break the cycle that would otherwise occur + // if textDidChangeInTextField was called. + virtual void OnAutofillSuggestionAccepted( + WebCore::HTMLInputElement* text_field); + private: void ModifySelection(WebCore::Frame* frame, WebCore::KeyboardEvent* event); @@ -173,7 +180,7 @@ class EditorClientImpl : public WebCore::EditorClient { bool ShouldSpellcheckByDefault(); // Whether the last entered key was a backspace. - bool backspace_pressed_; + bool backspace_or_delete_pressed_; // This flag is set to false if spell check for this editor is manually // turned off. The default setting is SPELLCHECK_AUTOMATIC. diff --git a/webkit/glue/password_autocomplete_listener.cc b/webkit/glue/password_autocomplete_listener.cc index 1b4260c..80e51a4 100644 --- a/webkit/glue/password_autocomplete_listener.cc +++ b/webkit/glue/password_autocomplete_listener.cc @@ -8,7 +8,10 @@ #include "webkit/glue/password_autocomplete_listener.h" #undef LOG #include "base/logging.h" +#include "base/string_util.h" #include "webkit/glue/glue_util.h" +#include "webkit/glue/webframe_impl.h" +#include "webkit/glue/webview_impl.h" namespace webkit_glue { @@ -41,6 +44,19 @@ void HTMLInputDelegate::OnFinishedAutocompleting() { element_->dispatchFormControlChangeEvent(); } +void HTMLInputDelegate::RefreshAutofillPopup( + const std::vector<std::wstring>& suggestions, + int default_suggestion_index) { + WebFrameImpl* webframe = + WebFrameImpl::FromFrame(element_->document()->frame()); + WebViewImpl* webview = webframe->GetWebViewImpl(); + if (!webview) + return; + + int64 node_id = reinterpret_cast<int64>(element_); + webview->AutofillSuggestionsForNode(node_id, suggestions, 0); +} + PasswordAutocompleteListener::PasswordAutocompleteListener( HTMLInputDelegate* username_delegate, HTMLInputDelegate* password_delegate, @@ -71,12 +87,24 @@ void PasswordAutocompleteListener::OnBlur(WebCore::HTMLInputElement* element, void PasswordAutocompleteListener::OnInlineAutocompleteNeeded( WebCore::HTMLInputElement* element, - const std::wstring& user_input) { + const std::wstring& user_input, + bool backspace_or_delete, + bool with_suggestion_popup) { // If wait_for_username is true, we only autofill the password when // the username field is blurred (i.e not inline) with a matching // username string entered. if (data_.wait_for_username) return; + + if (with_suggestion_popup) { + std::vector<std::wstring> suggestions; + GetSuggestions(user_input, &suggestions); + username_delegate_->RefreshAutofillPopup(suggestions, 0); + } + + if (backspace_or_delete) + return; // Don't inline autocomplete when the user deleted something. + // Look for any suitable matches to current field text. // TODO(timsteele): The preferred login (in basic_data.values) and // additional logins could be bundled into the same data structure @@ -103,7 +131,7 @@ void PasswordAutocompleteListener::OnInlineAutocompleteNeeded( bool PasswordAutocompleteListener::TryToMatch(const std::wstring& input, const std::wstring& username, const std::wstring& password) { - if (input.compare(0, input.length(), username, 0, input.length()) != 0) + if (!StartsWith(username, input, false)) return false; // Input matches the username, fill in required values. @@ -115,4 +143,18 @@ bool PasswordAutocompleteListener::TryToMatch(const std::wstring& input, return true; } +void PasswordAutocompleteListener::GetSuggestions( + const std::wstring& input, std::vector<std::wstring>* suggestions) { + if (StartsWith(data_.basic_data.values[0], input, false)) + suggestions->push_back(data_.basic_data.values[0]); + + for (PasswordFormDomManager::LoginCollection::iterator it = + data_.additional_logins.begin(); + it != data_.additional_logins.end(); + ++it) { + if (StartsWith(it->first, input, false)) + suggestions->push_back(it->first); + } +} + } // webkit_glue diff --git a/webkit/glue/password_autocomplete_listener.h b/webkit/glue/password_autocomplete_listener.h index 9666397..c56c0189 100644 --- a/webkit/glue/password_autocomplete_listener.h +++ b/webkit/glue/password_autocomplete_listener.h @@ -32,6 +32,9 @@ class HTMLInputDelegate { virtual void SetValue(const std::wstring& value); virtual void SetSelectionRange(size_t start, size_t end); virtual void OnFinishedAutocompleting(); + virtual void RefreshAutofillPopup( + const std::vector<std::wstring>& suggestions, + int default_suggestion_index); private: // The underlying DOM element we're wrapping. We reference the underlying @@ -54,7 +57,9 @@ class PasswordAutocompleteListener { virtual void OnBlur(WebCore::HTMLInputElement* element, const std::wstring& user_input); virtual void OnInlineAutocompleteNeeded(WebCore::HTMLInputElement* element, - const std::wstring& user_input); + const std::wstring& user_input, + bool backspace_or_delete, + bool with_suggestion_popup); private: // Check if the input string resembles a potential matching login @@ -64,6 +69,10 @@ class PasswordAutocompleteListener { const std::wstring& username, const std::wstring& password); + // Scan |data_| for prefix matches of |input| and add each to |suggestions|. + void GetSuggestions(const std::wstring& input, + std::vector<std::wstring>* suggestions); + // Access to password field to autocomplete on blur/username updates. scoped_ptr<HTMLInputDelegate> password_delegate_; scoped_ptr<HTMLInputDelegate> username_delegate_; diff --git a/webkit/glue/password_autocomplete_listener_unittest.cc b/webkit/glue/password_autocomplete_listener_unittest.cc index 05337d9..3a12047 100644 --- a/webkit/glue/password_autocomplete_listener_unittest.cc +++ b/webkit/glue/password_autocomplete_listener_unittest.cc @@ -153,7 +153,7 @@ TEST_F(PasswordManagerAutocompleteTests, OnInlineAutocompleteNeeded) { password_delegate->SetValue(std::wstring()); // Simulate the user typing in the first letter of 'alice', a stored username. - listener->OnInlineAutocompleteNeeded(NULL, L"a"); + listener->OnInlineAutocompleteNeeded(NULL, L"a", false, false); // Both the username and password delegates should reflect selection // of the stored login. EXPECT_EQ(username1_, username_delegate->value()); @@ -166,7 +166,7 @@ TEST_F(PasswordManagerAutocompleteTests, OnInlineAutocompleteNeeded) { EXPECT_TRUE(password_delegate->did_call_on_finish()); // Now the user types the next letter of the same username, 'l'. - listener->OnInlineAutocompleteNeeded(NULL, L"al"); + listener->OnInlineAutocompleteNeeded(NULL, L"al", false, false); // Now the fields should have the same value, but the selection should have a // different start value. EXPECT_EQ(username1_, username_delegate->value()); @@ -186,7 +186,7 @@ TEST_F(PasswordManagerAutocompleteTests, OnInlineAutocompleteNeeded) { // was invoked during OnInlineAutocompleteNeeded. username_delegate->ResetTestState(); password_delegate->ResetTestState(); - listener->OnInlineAutocompleteNeeded(NULL, L"alf"); + listener->OnInlineAutocompleteNeeded(NULL, L"alf", false, false); EXPECT_FALSE(username_delegate->did_set_selection()); EXPECT_FALSE(username_delegate->did_set_value()); EXPECT_FALSE(username_delegate->did_call_on_finish()); @@ -194,7 +194,7 @@ TEST_F(PasswordManagerAutocompleteTests, OnInlineAutocompleteNeeded) { EXPECT_FALSE(password_delegate->did_call_on_finish()); // Ok, so now the user removes all the text and enters the letter 'b'. - listener->OnInlineAutocompleteNeeded(NULL, L"b"); + listener->OnInlineAutocompleteNeeded(NULL, L"b", false, false); // The username and password fields should match the 'bob' entry. EXPECT_EQ(username2_, username_delegate->value()); EXPECT_EQ(password2_, password_delegate->value()); @@ -220,13 +220,13 @@ TEST_F(PasswordManagerAutocompleteTests, TestWaitUsername) { // never modify it when wait_for_username is true; only the user can by // typing into (in real life) the HTMLInputElement. password_delegate->SetValue(std::wstring()); - listener->OnInlineAutocompleteNeeded(NULL, L"a"); + listener->OnInlineAutocompleteNeeded(NULL, L"a", false, false); EXPECT_EQ(empty, username_delegate->value()); EXPECT_EQ(empty, password_delegate->value()); - listener->OnInlineAutocompleteNeeded(NULL, L"al"); + listener->OnInlineAutocompleteNeeded(NULL, L"al", false, false); EXPECT_EQ(empty, username_delegate->value()); EXPECT_EQ(empty, password_delegate->value()); - listener->OnInlineAutocompleteNeeded(NULL, L"alice"); + listener->OnInlineAutocompleteNeeded(NULL, L"alice", false, false); EXPECT_EQ(empty, username_delegate->value()); EXPECT_EQ(empty, password_delegate->value()); diff --git a/webkit/glue/webview_impl.cc b/webkit/glue/webview_impl.cc index 250896e..fe9348d 100644 --- a/webkit/glue/webview_impl.cc +++ b/webkit/glue/webview_impl.cc @@ -182,6 +182,11 @@ class AutocompletePopupMenuClient : public WebCore::PopupMenuClient { // WebCore::PopupMenuClient implementation. virtual void valueChanged(unsigned listIndex, bool fireEvents = true) { text_field_->setValue(suggestions_[listIndex]); + EditorClientImpl* editor = + static_cast<EditorClientImpl*>(webview_->page()->editorClient()); + DCHECK(editor); + editor->OnAutofillSuggestionAccepted( + static_cast<WebCore::HTMLInputElement*>(text_field_.get())); } virtual WebCore::String itemText(unsigned list_index) const { |