diff options
author | jcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-20 01:28:55 +0000 |
---|---|---|
committer | jcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-20 01:28:55 +0000 |
commit | 2cd7eb8743299ff81a2c7054650b21c5a11dd376 (patch) | |
tree | c9001e097fa526b0488385a08b6adc9b0c9f1584 /webkit/glue | |
parent | 5c6f1c6b32f9da207706cf2d94560196e9d20303 (diff) | |
download | chromium_src-2cd7eb8743299ff81a2c7054650b21c5a11dd376.zip chromium_src-2cd7eb8743299ff81a2c7054650b21c5a11dd376.tar.gz chromium_src-2cd7eb8743299ff81a2c7054650b21c5a11dd376.tar.bz2 |
A new implementation of the autofill using the editor client API.
This simplifies code as we don't need to listen for events on input elements, the editor client API is only triggered when the text changes.
The only quirk we have to work around is that when the editor client API notifies us that the text has changed, the selection is not set properly, preventing us from reliably finding out if the caret is at the end of the text.
To work around that issue, we post a task that does the autofill after the text change callback.
BUG=None
TEST=Trigger the autofill behavior with form and passwords.
Review URL: http://codereview.chromium.org/11479
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@5742 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/glue')
-rw-r--r-- | webkit/glue/autocomplete_input_listener.cc | 231 | ||||
-rw-r--r-- | webkit/glue/autocomplete_input_listener_unittest.cc | 227 | ||||
-rw-r--r-- | webkit/glue/dom_operations.cc | 6 | ||||
-rw-r--r-- | webkit/glue/editor_client_impl.cc | 121 | ||||
-rw-r--r-- | webkit/glue/editor_client_impl.h | 21 | ||||
-rw-r--r-- | webkit/glue/form_autocomplete_listener.cc | 35 | ||||
-rw-r--r-- | webkit/glue/form_autocomplete_listener.h | 40 | ||||
-rw-r--r-- | webkit/glue/password_autocomplete_listener.cc | 30 | ||||
-rw-r--r-- | webkit/glue/password_autocomplete_listener.h | 38 | ||||
-rw-r--r-- | webkit/glue/password_autocomplete_listener_unittest.cc | 2 | ||||
-rw-r--r-- | webkit/glue/webframe_impl.cc | 30 | ||||
-rw-r--r-- | webkit/glue/webframe_impl.h | 34 | ||||
-rw-r--r-- | webkit/glue/webframeloaderclient_impl.cc | 64 | ||||
-rw-r--r-- | webkit/glue/webframeloaderclient_impl.h | 5 |
14 files changed, 208 insertions, 676 deletions
diff --git a/webkit/glue/autocomplete_input_listener.cc b/webkit/glue/autocomplete_input_listener.cc deleted file mode 100644 index 0aceb02..0000000 --- a/webkit/glue/autocomplete_input_listener.cc +++ /dev/null @@ -1,231 +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 provides an abstract implementation of the inline autocomplete -// infrastructure defined in autocomplete_input_listener.h. - -#include "webkit/glue/autocomplete_input_listener.h" -#include <set> - -MSVC_PUSH_WARNING_LEVEL(0); -#include "HTMLInputElement.h" -#include "HTMLFormElement.h" -#include "Document.h" -#include "Frame.h" -#include "Editor.h" -#include "EventNames.h" -#include "Event.h" -#include "HTMLNames.h" -MSVC_POP_WARNING(); - -#undef LOG - -#include "base/logging.h" -#include "webkit/glue/editor_client_impl.h" -#include "webkit/glue/glue_util.h" - -namespace webkit_glue { - -// Hack (1 of 2) for http://bugs.webkit.org/show_bug.cgi?id=16976. This bug -// causes the caret position to be set after handling input events, which -// trumps our modifications, so for now we tell the EditorClient to preserve -// whatever selection set by our code. -// TODO(timsteele): Remove this function altogether once bug is fixed. -static void PreserveSelection(WebCore::HTMLInputElement* element) { - WebCore::EditorClient* ec = - element->form()->document()->frame()->editor()->client(); - EditorClientImpl* client = static_cast<EditorClientImpl*>(ec); - client->PreserveSelection(); -} - -HTMLInputDelegate::HTMLInputDelegate(WebCore::HTMLInputElement* element) - : element_(element) { - // Reference the element for the lifetime of this delegate. - // e is NULL when testing. - if (element_) - element_->ref(); -} - -HTMLInputDelegate::~HTMLInputDelegate() { - if (element_) - element_->deref(); -} - -void HTMLInputDelegate::SetValue(const std::wstring& value) { - element_->setValue(StdWStringToString(value)); -} - -void HTMLInputDelegate::SetSelectionRange(size_t start, size_t end) { - element_->setSelectionRange(start, end); - // Hack, see comments for PreserveSelection(). - PreserveSelection(element_); -} - -void HTMLInputDelegate::OnFinishedAutocompleting() { - // This sets the input element to an autofilled state which will result in it - // having a yellow background. - element_->setAutofilled(true); - // Notify any changeEvent listeners. - element_->onChange(); -} - -AutocompleteBodyListener::AutocompleteBodyListener(WebCore::Frame* frame) { - WebCore::HTMLElement* body = frame->document()->body(); - body->addEventListener(WebCore::eventNames().DOMFocusOutEvent, this, false); - body->addEventListener(WebCore::eventNames().inputEvent, this, false); - // Attaching to the WebCore body element effectively transfers ownership of - // the listener object. When WebCore is tearing down the document, any - // attached listeners are destroyed. - // See Document::removeAllEventListenersFromAllNodes which is called by - // FrameLoader::stopLoading. Also, there is no need for matching calls to - // removeEventListener because the simplest and most convienient thing to do - // for autocompletion is to stop listening once the element is destroyed. -} - -AutocompleteBodyListener::~AutocompleteBodyListener() { - // Delete the listener. Pay special attention since we may have the same - // listener registered for several elements. - std::set<AutocompleteInputListener*> to_be_deleted_; - for (InputElementInfoMap::iterator iter = elements_info_.begin(); - iter != elements_info_.end(); ++iter) { - to_be_deleted_.insert(iter->second.listener); - } - - std::set<AutocompleteInputListener*>::iterator iter; - for (iter = to_be_deleted_.begin(); iter != to_be_deleted_.end(); ++iter) - delete *iter; - elements_info_.clear(); -} - -// The following 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 ***** */ -bool AutocompleteBodyListener::ShouldInlineAutocomplete( - WebCore::HTMLInputElement* input, - const std::wstring& old_text, - const std::wstring& new_text) { - size_t prev_length = old_text.length(); - // The following are a bunch of early returns in cases we don't want to - // go through with inline autocomplete. - - // Don't bother doing AC if nothing changed. - if (new_text.length() > 0 && (new_text == old_text)) - return false; - - // Did user backspace? - if ((new_text.length() < old_text.length()) && - old_text.substr(0, new_text.length()) == new_text) { - return false; - } - - // Is search string empty? - if (new_text.empty()) - return false; - return IsCaretAtEndOfText(input, new_text.length(), prev_length); -} - -void AutocompleteBodyListener::handleEvent(WebCore::Event* event, - bool /*is_window_event*/) { - const WebCore::AtomicString& webcore_type = event->type(); - DCHECK(event->target()->toNode()); - if (!event->target()->toNode()->hasTagName(WebCore::HTMLNames::inputTag)) - return; // Not a node of interest to us. - - WebCore::HTMLInputElement* input = - static_cast<WebCore::HTMLInputElement*>(event->target()->toNode()); - InputElementInfoMap::const_iterator iter = elements_info_.find(input); - if (iter == elements_info_.end()) - return; // Not an input node we are listening to. - - InputElementInfo input_info = iter->second; - const std::wstring& user_input = StringToStdWString(input->value()); - if (webcore_type == WebCore::eventNames().DOMFocusOutEvent) { - input_info.listener->OnBlur(input, user_input); - } else if (webcore_type == WebCore::eventNames().inputEvent) { - // Perform inline autocomplete if it is safe to do so. - if (ShouldInlineAutocomplete(input, - input_info.previous_text, user_input)) { - input_info.listener->OnInlineAutocompleteNeeded(input, user_input); - } - // Update the info. - input_info.previous_text = user_input; - elements_info_[input] = input_info; - } else { - NOTREACHED() << "unexpected EventName for autocomplete listener"; - } -} - -void AutocompleteBodyListener::AddInputListener( - WebCore::HTMLInputElement* element, - AutocompleteInputListener* listener) { - DCHECK(elements_info_.find(element) == elements_info_.end()); - InputElementInfo elem_info; - elem_info.listener = listener; - elements_info_[element] = elem_info; -} - -bool AutocompleteBodyListener::IsCaretAtEndOfText( - WebCore::HTMLInputElement* element, - size_t input_length, - size_t previous_length) const { - // Hack 2 of 2 for http://bugs.webkit.org/show_bug.cgi?id=16976. - // TODO(timsteele): This check should only return early if - // !(selectionEnd == selectionStart == user_input.length()). - // However, because of webkit bug #16976 the caret is not properly moved - // until after the handlers have executed, so for now we do the following - // several checks. The first check handles the case webkit sets the End - // selection but not the Start selection correctly, and the second is for - // when webcore sets neither. This won't be perfect if the user moves the - // selection around during inline autocomplete, but for now its the - // friendliest behavior we can offer. Once the bug is fixed this method - // should no longer need the previous_length parameter. - if (((element->selectionEnd() != element->selectionStart() + 1) || - (element->selectionEnd() != static_cast<int>(input_length))) && - ((element->selectionEnd() != element->selectionStart()) || - (element->selectionEnd() != static_cast<int>(previous_length)))) { - return false; - } - return true; -} -} // webkit_glue diff --git a/webkit/glue/autocomplete_input_listener_unittest.cc b/webkit/glue/autocomplete_input_listener_unittest.cc deleted file mode 100644 index 0876d1c..0000000 --- a/webkit/glue/autocomplete_input_listener_unittest.cc +++ /dev/null @@ -1,227 +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. -// -// The DomAutocompleteTests in this file are responsible for ensuring the -// abstract dom autocomplete framework is correctly responding to events and -// delegating to appropriate places. This means concrete implementations should -// focus only on testing the code actually written for that implementation and -// those tests should be completely decoupled from WebCore::Event. - -#include <string> - -#include "config.h" - -#include "base/compiler_specific.h" - -MSVC_PUSH_WARNING_LEVEL(0); -#include "HTMLInputElement.h" -#include "HTMLFormElement.h" -#include "Document.h" -#include "Frame.h" -#include "Editor.h" -#include "EventNames.h" -#include "Event.h" -#include "EventListener.h" -#include <wtf/Threading.h> -MSVC_POP_WARNING(); - -#undef LOG - -#include "webkit/glue/autocomplete_input_listener.h" -#include "webkit/glue/webframe.h" -#include "webkit/glue/webframe_impl.h" -#include "webkit/glue/webview.h" -#include "webkit/tools/test_shell/test_shell_test.h" -#include "testing/gtest/include/gtest/gtest.h" - -using WebCore::Event; - -namespace webkit_glue { - -class TestAutocompleteBodyListener : public AutocompleteBodyListener { - public: - TestAutocompleteBodyListener() { - } - - void SetCaretAtEnd(WebCore::HTMLInputElement* element, bool value) { - std::vector<WebCore::HTMLInputElement*>::iterator iter = - std::find(caret_at_end_elements_.begin(), caret_at_end_elements_.end(), - element); - if (value) { - if (iter == caret_at_end_elements_.end()) - caret_at_end_elements_.push_back(element); - } else { - if (iter != caret_at_end_elements_.end()) - caret_at_end_elements_.erase(iter); - } - } - - void ResetTestState() { - caret_at_end_elements_.clear(); - } - - protected: - // AutocompleteBodyListener override. - virtual bool IsCaretAtEndOfText(WebCore::HTMLInputElement* element, - size_t input_length, - size_t previous_length) const { - return std::find(caret_at_end_elements_.begin(), - caret_at_end_elements_.end(), - element) != caret_at_end_elements_.end(); - } - - private: - // List of elements for which the caret is at the end of the text. - std::vector<WebCore::HTMLInputElement*> caret_at_end_elements_; -}; - -class TestAutocompleteInputListener : public AutocompleteInputListener { - public: - TestAutocompleteInputListener() - : blurred_(false), - did_request_inline_autocomplete_(false) { - } - - void ResetTestState() { - blurred_ = false; - did_request_inline_autocomplete_ = false; - } - - bool blurred() const { return blurred_; } - bool did_request_inline_autocomplete() const { - return did_request_inline_autocomplete_; - } - - virtual void OnBlur(WebCore::HTMLInputElement* element, - const std::wstring& user_input) { - blurred_ = true; - } - virtual void OnInlineAutocompleteNeeded(WebCore::HTMLInputElement* element, - const std::wstring& user_input) { - did_request_inline_autocomplete_ = true; - } - - private: - bool blurred_; - bool did_request_inline_autocomplete_; -}; - -namespace { - -class DomAutocompleteTests : public TestShellTest { - public: - virtual void SetUp() { - TestShellTest::SetUp(); - // We need a document in order to create HTMLInputElements. - WebView* view = test_shell_->webView(); - WebFrameImpl* frame = static_cast<WebFrameImpl*>(view->GetMainFrame()); - document_ = frame->frame()->document(); - } - - void FireAndHandleInputEvent(AutocompleteBodyListener* listener, - WebCore::HTMLInputElement* element) { - RefPtr<Event> event(Event::create(WebCore::eventNames().inputEvent, - false, false)); - event->setTarget(element); - listener->handleEvent(event.get(), false); - } - - void SimulateTypedInput(TestAutocompleteBodyListener* listener, - WebCore::HTMLInputElement* element, - const std::wstring& new_input, - bool caret_at_end) { - element->setValue(StdWStringToString(new_input)); - listener->SetCaretAtEnd(element, caret_at_end); - FireAndHandleInputEvent(listener, element); - } - - WebCore::Document* document_; -}; -} // namespace - -TEST_F(DomAutocompleteTests, OnBlur) { - RefPtr<WebCore::HTMLInputElement> ignored_element = - new WebCore::HTMLInputElement(document_); - RefPtr<WebCore::HTMLInputElement> listened_element = - new WebCore::HTMLInputElement(document_); - RefPtr<TestAutocompleteBodyListener> body_listener = - adoptRef(new TestAutocompleteBodyListener); - TestAutocompleteInputListener* listener = new TestAutocompleteInputListener(); - // body_listener takes ownership of the listener. - body_listener->AddInputListener(listened_element.get(), listener); - - // Simulate a blur event to the element we are not listening to. - // Our listener should not be notified. - RefPtr<Event> event(Event::create(WebCore::eventNames().DOMFocusOutEvent, - false, false)); - event->setTarget(ignored_element.get()); - body_listener->handleEvent(event.get(), false); - EXPECT_FALSE(listener->blurred()); - - // Now simulate the event on the input element we are listening to. - event->setTarget(listened_element.get()); - body_listener->handleEvent(event.get(), false); - EXPECT_TRUE(listener->blurred()); -} - -TEST_F(DomAutocompleteTests, InlineAutocompleteTriggeredByInputEvent) { - RefPtr<WebCore::HTMLInputElement> ignored_element = - new WebCore::HTMLInputElement(document_); - RefPtr<WebCore::HTMLInputElement> listened_element = - new WebCore::HTMLInputElement(document_); - RefPtr<TestAutocompleteBodyListener> body_listener = - adoptRef(new TestAutocompleteBodyListener()); - - TestAutocompleteInputListener* listener = new TestAutocompleteInputListener(); - body_listener->AddInputListener(listened_element.get(), listener); - - // Simulate an inputEvent by setting the value and artificially firing evt. - // The user typed 'g'. - SimulateTypedInput(body_listener.get(), ignored_element.get(), L"g", true); - EXPECT_FALSE(listener->did_request_inline_autocomplete()); - SimulateTypedInput(body_listener.get(), listened_element.get(), L"g", true); - EXPECT_TRUE(listener->did_request_inline_autocomplete()); -} - -TEST_F(DomAutocompleteTests, InlineAutocompleteHeuristics) { - RefPtr<WebCore::HTMLInputElement> input_element = - new WebCore::HTMLInputElement(document_); - RefPtr<TestAutocompleteBodyListener> body_listener = - adoptRef(new TestAutocompleteBodyListener()); - - TestAutocompleteInputListener* listener = new TestAutocompleteInputListener(); - body_listener->AddInputListener(input_element.get(), listener); - - // Simulate a user entering some text, and then backspacing to remove - // a character. - SimulateTypedInput(body_listener.get(), input_element.get(), L"g", true); - EXPECT_TRUE(listener->did_request_inline_autocomplete()); - listener->ResetTestState(); - body_listener->ResetTestState(); - - SimulateTypedInput(body_listener.get(), input_element.get(), L"go", true); - EXPECT_TRUE(listener->did_request_inline_autocomplete()); - listener->ResetTestState(); - body_listener->ResetTestState(); - - SimulateTypedInput(body_listener.get(), input_element.get(), L"g", true); - EXPECT_FALSE(listener->did_request_inline_autocomplete()); - listener->ResetTestState(); - body_listener->ResetTestState(); - - // Now simulate the user moving the cursor to a position other than the end, - // and adding text. - SimulateTypedInput(body_listener.get(), input_element.get(), L"og", false); - EXPECT_FALSE(listener->did_request_inline_autocomplete()); - listener->ResetTestState(); - body_listener->ResetTestState(); - - // Test that same input doesn't trigger autocomplete. - SimulateTypedInput(body_listener.get(), input_element.get(), L"og", true); - EXPECT_FALSE(listener->did_request_inline_autocomplete()); - listener->ResetTestState(); - body_listener->ResetTestState(); -} - -} // webkit_glue diff --git a/webkit/glue/dom_operations.cc b/webkit/glue/dom_operations.cc index 6b560c8..a488573 100644 --- a/webkit/glue/dom_operations.cc +++ b/webkit/glue/dom_operations.cc @@ -27,10 +27,6 @@ MSVC_PUSH_WARNING_LEVEL(0); MSVC_POP_WARNING(); #undef LOG -// Brings in more WebKit headers and #undefs LOG again, so this needs to come -// first. -#include "webkit/glue/autocomplete_input_listener.h" - #include "base/string_util.h" #include "webkit/glue/dom_operations.h" #include "webkit/glue/form_data.h" @@ -449,7 +445,7 @@ void FillPasswordForm(WebView* view, static_cast<WebFrameLoaderClient*>(username_element->document()-> frame()->loader()->client()); WebFrameImpl* webframe_impl = frame_loader_client->webframe(); - webframe_impl->GetAutocompleteListener()->AddInputListener( + webframe_impl->RegisterPasswordListener( username_element, new PasswordAutocompleteListener( new HTMLInputDelegate(username_element), diff --git a/webkit/glue/editor_client_impl.cc b/webkit/glue/editor_client_impl.cc index 722d5fb..27c2657 100644 --- a/webkit/glue/editor_client_impl.cc +++ b/webkit/glue/editor_client_impl.cc @@ -17,6 +17,7 @@ MSVC_PUSH_WARNING_LEVEL(0); #include "EventNames.h" #include "KeyboardCodes.h" #include "HTMLInputElement.h" +#include "HTMLNames.h" #include "Frame.h" #include "KeyboardEvent.h" #include "PlatformKeyboardEvent.h" @@ -24,6 +25,7 @@ MSVC_PUSH_WARNING_LEVEL(0); MSVC_POP_WARNING(); #undef LOG +#include "base/message_loop.h" #include "base/string_util.h" #include "webkit/glue/editor_client_impl.h" #include "webkit/glue/glue_util.h" @@ -37,6 +39,10 @@ MSVC_POP_WARNING(); // into a single action. static const size_t kMaximumUndoStackDepth = 1000; +// The size above which we stop triggering autofill for an input text field +// (so to avoid sending long strings through IPC). +static const size_t kMaximumTextSizeForAutofill = 1000; + namespace { // Record an editor command from the keyDownEntries[] below. We ignore the @@ -63,8 +69,11 @@ EditorClientImpl::EditorClientImpl(WebView* web_view) : web_view_(static_cast<WebViewImpl*>(web_view)), use_editor_delegate_(false), in_redo_(false), - preserve_(false), - pending_inline_autocompleted_element_(NULL) { + backspace_pressed_(false), +// Don't complain about using "this" in initializer list. +MSVC_PUSH_DISABLE_WARNING(4355) + autofill_factory_(this) { +MSVC_POP_WARNING() } EditorClientImpl::~EditorClientImpl() { @@ -182,10 +191,6 @@ bool EditorClientImpl::shouldDeleteRange(WebCore::Range* range) { return true; } -void EditorClientImpl::PreserveSelection() { - preserve_ = true; -} - bool EditorClientImpl::shouldChangeSelectedRange(WebCore::Range* fromRange, WebCore::Range* toRange, WebCore::EAffinity affinity, @@ -200,12 +205,6 @@ bool EditorClientImpl::shouldChangeSelectedRange(WebCore::Range* fromRange, stillSelecting); } } - // Have we been told to preserve the selection? - // (See comments for PreserveSelection in header). - if (preserve_) { - preserve_ = false; - return false; - } return true; } @@ -242,25 +241,6 @@ void EditorClientImpl::respondToChangedSelection() { } void EditorClientImpl::respondToChangedContents() { - // Ugly Hack. (See also webkit bug #16976). - // Something is wrong with webcore's focusController in that when selection - // is set to a region within a text element when handling an input event, if - // you don't re-focus the node then it only _APPEARS_ to have successfully - // changed the selection (the UI "looks" right) but in reality there is no - // selection of text. And to make matters worse, you can't just re-focus it, - // you have to re-focus it in code executed after the entire event listener - // loop has finished; and hence here we are. Oh, and to make matters worse, - // this sequence of events _doesn't_ happen when you debug through the code - // -- in that case it works perfectly fine -- because swapping to the debugger - // causes the refocusing we artificially reproduce here. - // TODO (timsteele): Clean this up once root webkit problem is identified and - // the bug is patched. - if (pending_inline_autocompleted_element_) { - pending_inline_autocompleted_element_->blur(); - pending_inline_autocompleted_element_->focus(); - pending_inline_autocompleted_element_ = NULL; - } - if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) @@ -621,17 +601,84 @@ void EditorClientImpl::textFieldDidBeginEditing(WebCore::Element*) { void EditorClientImpl::textFieldDidEndEditing(WebCore::Element*) { // Notification that focus was lost. Be careful with this, it's also sent // when the page is being closed. + + // Cancel any pending DoAutofill calls. + autofill_factory_.RevokeAll(); } void EditorClientImpl::textDidChangeInTextField(WebCore::Element* element) { - // Track the element so we can blur/focus it in respondToChangedContents - // so that the selected range is properly set. (See respondToChangedContents). - if (static_cast<WebCore::HTMLInputElement*>(element)->autofilled()) - pending_inline_autocompleted_element_ = element; + DCHECK(element->hasLocalName(WebCore::HTMLNames::inputTag)); + + // Cancel any pending DoAutofill calls. + autofill_factory_.RevokeAll(); + + // Let's try to trigger autofill for that field, if applicable. + WebCore::HTMLInputElement* input_element = + static_cast<WebCore::HTMLInputElement*>(element); + if (!input_element->isEnabled() || !input_element->isTextField() || + input_element->isPasswordField() || !input_element->autoComplete()) { + return; + } + + std::wstring name = webkit_glue::StringToStdWString(input_element->name()); + if (name.empty()) // If the field has no name, then we won't have values. + return; + + // Don't attempt to autofill with values that are too large. + if (input_element->value().length() > kMaximumTextSizeForAutofill) + return; + + // 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 we need it to determine whether or not to trigger autofill. + std::wstring value = webkit_glue::StringToStdWString(input_element->value()); + MessageLoop::current()->PostTask( + FROM_HERE, + autofill_factory_.NewRunnableMethod(&EditorClientImpl::DoAutofill, + input_element, + backspace_pressed_)); +} + +void EditorClientImpl::DoAutofill(WebCore::HTMLInputElement* input_element, + bool backspace) { + std::wstring value = webkit_glue::StringToStdWString(input_element->value()); + + // Only autofill when there is some text and the caret is at the end. + bool caret_at_end = + input_element->selectionStart() == input_element->selectionEnd() && + input_element->selectionEnd() == static_cast<int>(value.length()); + if (value.empty() || !caret_at_end) + return; + + // First let's see if there is a password listener for that element. + WebFrameImpl* webframe = + WebFrameImpl::FromFrame(input_element->form()->document()->frame()); + webkit_glue::PasswordAutocompleteListener* listener = + webframe->GetPasswordListener(input_element); + if (listener) { + if (backspace) // No autocomplete for password on backspace. + return; + + listener->OnInlineAutocompleteNeeded(input_element, value); + return; + } + + // Then trigger form autofill. + std::wstring name = webkit_glue::StringToStdWString(input_element-> + name().string()); + web_view_->delegate()->QueryFormFieldAutofill(name, value, + reinterpret_cast<int64>(input_element)); } -bool EditorClientImpl::doTextFieldCommandFromEvent(WebCore::Element*, - WebCore::KeyboardEvent*) { +bool EditorClientImpl::doTextFieldCommandFromEvent( + WebCore::Element* element, + WebCore::KeyboardEvent* event) { + // Remember if backspace was pressed for the autofill. It is not clear how to + // 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); + // The Mac code appears to use this method as a hook to implement special // keyboard commands specific to Safari's auto-fill implementation. We // just return false to allow the default action. diff --git a/webkit/glue/editor_client_impl.h b/webkit/glue/editor_client_impl.h index 189a9af..4d0f057 100644 --- a/webkit/glue/editor_client_impl.h +++ b/webkit/glue/editor_client_impl.h @@ -6,6 +6,7 @@ #define WEBKIT_GLUE_EDITOR_CLIENT_IMPL_H__ #include "base/compiler_specific.h" +#include "base/task.h" #include "build/build_config.h" @@ -17,6 +18,7 @@ MSVC_POP_WARNING(); namespace WebCore { class Frame; +class HTMLInputElement; class Node; class PlatformKeyboardEvent; } @@ -103,9 +105,6 @@ class EditorClientImpl : public WebCore::EditorClient { virtual void setInputMethodState(bool enabled); void SetUseEditorDelegate(bool value) { use_editor_delegate_ = value; } - // HACK for webkit bug #16976. - // TODO (timsteele): Clean this up once webkit bug 16976 is fixed. - void PreserveSelection(); // It would be better to add these methods to the objects they describe, but // those are in WebCore and therefore inaccessible. @@ -123,21 +122,23 @@ class EditorClientImpl : public WebCore::EditorClient { void ModifySelection(WebCore::Frame* frame, WebCore::KeyboardEvent* event); + void DoAutofill(WebCore::HTMLInputElement* input_element, bool backspace); + protected: WebViewImpl* web_view_; bool use_editor_delegate_; bool in_redo_; - // Should preserve the selection in next call to shouldChangeSelectedRange. - bool preserve_; - - // Points to an HTMLInputElement that was just autocompleted (else NULL), - // for use by respondToChangedContents(). - WebCore::Element* pending_inline_autocompleted_element_; - typedef std::deque<WTF::RefPtr<WebCore::EditCommand> > EditCommandStack; EditCommandStack undo_stack_; EditCommandStack redo_stack_; + + private: + // Whether the last entered key was a backspace. + bool backspace_pressed_; + + // The method factory used to post autofill related tasks. + ScopedRunnableMethodFactory<EditorClientImpl> autofill_factory_; }; #endif // WEBKIT_GLUE_EDITOR_CLIENT_IMPL_H__ diff --git a/webkit/glue/form_autocomplete_listener.cc b/webkit/glue/form_autocomplete_listener.cc deleted file mode 100644 index e5751ca..0000000 --- a/webkit/glue/form_autocomplete_listener.cc +++ /dev/null @@ -1,35 +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. - -#include "config.h" - -#include "webkit/glue/form_autocomplete_listener.h" - -MSVC_PUSH_WARNING_LEVEL(0); -#include "HTMLInputElement.h" -MSVC_POP_WARNING(); - -#undef LOG - -#include "webkit/glue/autocomplete_input_listener.h" -#include "webkit/glue/glue_util.h" -#include "webkit/glue/webview_delegate.h" - -namespace webkit_glue { - -FormAutocompleteListener::FormAutocompleteListener( - WebViewDelegate* webview_delegate) - : webview_delegate_(webview_delegate) { -} - -void FormAutocompleteListener::OnInlineAutocompleteNeeded( - WebCore::HTMLInputElement* input_element, - const std::wstring& user_input) { - std::wstring name = webkit_glue::StringToStdWString(input_element-> - name().string()); - webview_delegate_->QueryFormFieldAutofill(name, user_input, - reinterpret_cast<int64>(input_element)); -} - -} // namespace diff --git a/webkit/glue/form_autocomplete_listener.h b/webkit/glue/form_autocomplete_listener.h deleted file mode 100644 index 1c86c02..0000000 --- a/webkit/glue/form_autocomplete_listener.h +++ /dev/null @@ -1,40 +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. - -#ifndef WEBKIT_GLUE_FORM_AUTOCOMPLETE_LISTENER_ -#define WEBKIT_GLUE_FORM_AUTOCOMPLETE_LISTENER_ - -#include <string> - -#include "webkit/glue/autocomplete_input_listener.h" - -class WebViewDelegate; - -namespace webkit_glue { - -// This class listens for the user typing in a text input in a form and queries -// the browser for autofill information. - -class FormAutocompleteListener : public AutocompleteInputListener { - public: - explicit FormAutocompleteListener(WebViewDelegate* webview_delegate); - virtual ~FormAutocompleteListener() { } - - // AutocompleteInputListener implementation. - virtual void OnBlur(WebCore::HTMLInputElement* input_element, - const std::wstring& user_input) { } - virtual void OnInlineAutocompleteNeeded(WebCore::HTMLInputElement* element, - const std::wstring& user_input); - - private: - // The delegate associated with the WebView that contains thhe input we are - // listening to. - WebViewDelegate* webview_delegate_; - - DISALLOW_COPY_AND_ASSIGN(FormAutocompleteListener); -}; - -} // webkit_glue - -#endif // WEBKIT_GLUE_FORM_AUTOCOMPLETE_LISTENER_ diff --git a/webkit/glue/password_autocomplete_listener.cc b/webkit/glue/password_autocomplete_listener.cc index fff24f4..2a96ae4 100644 --- a/webkit/glue/password_autocomplete_listener.cc +++ b/webkit/glue/password_autocomplete_listener.cc @@ -8,9 +8,39 @@ #include "webkit/glue/password_autocomplete_listener.h" #undef LOG #include "base/logging.h" +#include "webkit/glue/glue_util.h" namespace webkit_glue { +HTMLInputDelegate::HTMLInputDelegate(WebCore::HTMLInputElement* element) + : element_(element) { + // Reference the element for the lifetime of this delegate. + // element is NULL when testing. + if (element_) + element_->ref(); +} + +HTMLInputDelegate::~HTMLInputDelegate() { + if (element_) + element_->deref(); +} + +void HTMLInputDelegate::SetValue(const std::wstring& value) { + element_->setValue(StdWStringToString(value)); +} + +void HTMLInputDelegate::SetSelectionRange(size_t start, size_t end) { + element_->setSelectionRange(start, end); +} + +void HTMLInputDelegate::OnFinishedAutocompleting() { + // This sets the input element to an autofilled state which will result in it + // having a yellow background. + element_->setAutofilled(true); + // Notify any changeEvent listeners. + element_->onChange(); +} + PasswordAutocompleteListener::PasswordAutocompleteListener( HTMLInputDelegate* username_delegate, HTMLInputDelegate* password_delegate, diff --git a/webkit/glue/password_autocomplete_listener.h b/webkit/glue/password_autocomplete_listener.h index a2c7a7a..9d74797 100644 --- a/webkit/glue/password_autocomplete_listener.h +++ b/webkit/glue/password_autocomplete_listener.h @@ -8,13 +8,42 @@ #ifndef WEBKIT_GLUE_PASSWORD_AUTOCOMPLETE_LISTENER_H_ #define WEBKIT_GLUE_PASSWORD_AUTOCOMPLETE_LISTENER_H_ +#include "config.h" + +#include "base/compiler_specific.h" + +MSVC_PUSH_WARNING_LEVEL(0); +#include "HTMLInputElement.h" +MSVC_POP_WARNING(); + #include "base/basictypes.h" -#include "webkit/glue/autocomplete_input_listener.h" +#include "base/scoped_ptr.h" #include "webkit/glue/password_form_dom_manager.h" namespace webkit_glue { -class PasswordAutocompleteListener : public AutocompleteInputListener { +// A proxy interface to a WebCore::HTMLInputElement for inline autocomplete. +// 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 PasswordAutocompleteListener { public: PasswordAutocompleteListener(HTMLInputDelegate* username_delegate, HTMLInputDelegate* password_delegate, @@ -22,7 +51,6 @@ class PasswordAutocompleteListener : public AutocompleteInputListener { virtual ~PasswordAutocompleteListener() { } - // AutocompleteInputListener implementation. virtual void OnBlur(WebCore::HTMLInputElement* element, const std::wstring& user_input); virtual void OnInlineAutocompleteNeeded(WebCore::HTMLInputElement* element, @@ -30,8 +58,8 @@ class PasswordAutocompleteListener : public AutocompleteInputListener { private: // Check if the input string resembles a potential matching login - // (username/password) and if so, match them up by autocompleting - // the edit delegates. + // (username/password) and if so, match them up by autocompleting the edit + // delegates. bool TryToMatch(const std::wstring& input, const std::wstring& username, const std::wstring& password); diff --git a/webkit/glue/password_autocomplete_listener_unittest.cc b/webkit/glue/password_autocomplete_listener_unittest.cc index 85cfa9f..e4b4e79 100644 --- a/webkit/glue/password_autocomplete_listener_unittest.cc +++ b/webkit/glue/password_autocomplete_listener_unittest.cc @@ -24,11 +24,9 @@ MSVC_POP_WARNING(); #undef LOG -#include "webkit/glue/autocomplete_input_listener.h" #include "webkit/glue/password_autocomplete_listener.h" #include "testing/gtest/include/gtest/gtest.h" -using webkit_glue::AutocompleteInputListener; using webkit_glue::PasswordAutocompleteListener; using webkit_glue::HTMLInputDelegate; diff --git a/webkit/glue/webframe_impl.cc b/webkit/glue/webframe_impl.cc index fa9960c..4ee45f9 100644 --- a/webkit/glue/webframe_impl.cc +++ b/webkit/glue/webframe_impl.cc @@ -286,8 +286,7 @@ MSVC_POP_WARNING() frames_scoping_count_(-1), scoping_complete_(false), next_invalidate_after_(0), - printing_(false), - form_autocomplete_listener_(NULL) { + printing_(false) { StatsCounter(kWebFrameActiveCount).Increment(); live_object_count_++; } @@ -297,6 +296,7 @@ WebFrameImpl::~WebFrameImpl() { live_object_count_--; CancelPendingScopingEffort(); + ClearPasswordListeners(); } // WebFrame ------------------------------------------------------------------- @@ -1873,14 +1873,24 @@ int WebFrameImpl::PendingFrameUnloadEventCount() const { return frame()->eventHandler()->pendingFrameUnloadEventCount(); } -webkit_glue::AutocompleteBodyListener* WebFrameImpl::GetAutocompleteListener() { - if (!form_autocomplete_listener_) { - form_autocomplete_listener_ = - adoptRef(new webkit_glue::AutocompleteBodyListener(frame())); - } - return form_autocomplete_listener_.get(); +void WebFrameImpl::RegisterPasswordListener( + PassRefPtr<WebCore::HTMLInputElement> input_element, + webkit_glue::PasswordAutocompleteListener* listener) { + RefPtr<WebCore::HTMLInputElement> element = input_element; + DCHECK(password_listeners_.find(element) == password_listeners_.end()); + password_listeners_.set(element, listener); } -void WebFrameImpl::ClearAutocompleteListener() { - form_autocomplete_listener_ = NULL; +webkit_glue::PasswordAutocompleteListener* WebFrameImpl::GetPasswordListener( + WebCore::HTMLInputElement* input_element) { + return password_listeners_.get(input_element); } + +void WebFrameImpl::ClearPasswordListeners() { + for (PasswordListenerMap::iterator iter = password_listeners_.begin(); + iter != password_listeners_.end(); ++iter) { + delete iter->second; + } + password_listeners_.clear(); +} + diff --git a/webkit/glue/webframe_impl.h b/webkit/glue/webframe_impl.h index b3b241a..d50762d 100644 --- a/webkit/glue/webframe_impl.h +++ b/webkit/glue/webframe_impl.h @@ -33,7 +33,7 @@ #include "base/gfx/platform_canvas.h" #include "base/scoped_ptr.h" #include "base/task.h" -#include "webkit/glue/form_autocomplete_listener.h" +#include "webkit/glue/password_autocomplete_listener.h" #include "webkit/glue/webdatasource_impl.h" #include "webkit/glue/webframe.h" #include "webkit/glue/webframeloaderclient_impl.h" @@ -271,14 +271,20 @@ class WebFrameImpl : public WebFrame { virtual bool IsReloadAllowingStaleData() const; - // Returns the listener used for autocomplete. Creates it and registers it on - // the frame body node on the first invocation. - webkit_glue::AutocompleteBodyListener* GetAutocompleteListener(); - - // Nulls the autocomplete listener for this frame. Useful as a frame might - // be reused (on reload for example), in which case a new body element is - // created and the existing autocomplete listener becomes useless. - void ClearAutocompleteListener(); + // Registers a listener for the specified user name input element. The + // listener will receive notifications for blur and when autocomplete should + // be triggered. + // The WebFrameImpl becomes the owner of the passed listener. + void RegisterPasswordListener( + PassRefPtr<WebCore::HTMLInputElement> user_name_input_element, + webkit_glue::PasswordAutocompleteListener* listener); + + // Returns the password autocomplete listener associated with the passed + // user name input element, or NULL if none available. + // Note that the returned listener is owner by the WebFrameImpl and should not + // be kept around as it is deleted when the page goes away. + webkit_glue::PasswordAutocompleteListener* GetPasswordListener( + WebCore::HTMLInputElement* user_name_input_element); protected: friend class WebFrameLoaderClient; @@ -440,14 +446,20 @@ class WebFrameImpl : public WebFrame { const WebCore::SubstituteData& data, bool replace); + // Clears the map of password listeners. + void ClearPasswordListeners(); + // In "printing" mode. Used as a state check. bool printing_; // For each printed page, the view of the document in pixels. Vector<WebCore::IntRect> pages_; - // The listener responsible for showing form autocomplete suggestions. - RefPtr<webkit_glue::AutocompleteBodyListener> form_autocomplete_listener_; + // The input fields that are interested in edit events and their associated + // listeners. + typedef HashMap<RefPtr<WebCore::HTMLInputElement>, + webkit_glue::PasswordAutocompleteListener*> PasswordListenerMap; + PasswordListenerMap password_listeners_; DISALLOW_COPY_AND_ASSIGN(WebFrameImpl); }; diff --git a/webkit/glue/webframeloaderclient_impl.cc b/webkit/glue/webframeloaderclient_impl.cc index f311ce1..5983854 100644 --- a/webkit/glue/webframeloaderclient_impl.cc +++ b/webkit/glue/webframeloaderclient_impl.cc @@ -44,8 +44,6 @@ MSVC_POP_WARNING(); #endif #include "webkit/glue/autofill_form.h" #include "webkit/glue/alt_404_page_resource_fetcher.h" -#include "webkit/glue/autocomplete_input_listener.h" -#include "webkit/glue/form_autocomplete_listener.h" #include "webkit/glue/glue_util.h" #include "webkit/glue/password_form_dom_manager.h" #include "webkit/glue/plugins/plugin_list.h" @@ -328,9 +326,10 @@ void WebFrameLoaderClient::dispatchDidFailLoading(DocumentLoader* loader, void WebFrameLoaderClient::dispatchDidFinishDocumentLoad() { WebViewImpl* webview = webframe_->webview_impl(); WebViewDelegate* d = webview->delegate(); - // A frame may be reused. This call ensures a new AutoCompleteListener will - // be created for the newly created frame. - webframe_->ClearAutocompleteListener(); + + // A frame may be reused. This call ensures we don't hold on to our password + // listeners and their associated HTMLInputElements. + webframe_->ClearPasswordListeners(); // The document has now been fully loaded. // Scan for password forms to be sent to the browser @@ -351,28 +350,11 @@ void WebFrameLoaderClient::dispatchDidFinishDocumentLoad() { if (!form->autoComplete()) continue; - std::set<std::wstring> password_related_fields; scoped_ptr<PasswordForm> passwordFormPtr( PasswordFormDomManager::CreatePasswordForm(form)); - if (passwordFormPtr.get()) { + if (passwordFormPtr.get()) passwordForms.push_back(*passwordFormPtr); - - // Let's remember the names of password related fields so we do not - // autofill them with the regular form autofill. - - if (!passwordFormPtr->username_element.empty()) - password_related_fields.insert(passwordFormPtr->username_element); - DCHECK(!passwordFormPtr->password_element.empty()); - password_related_fields.insert(passwordFormPtr->password_element); - if (!passwordFormPtr->old_password_element.empty()) - password_related_fields.insert(passwordFormPtr->old_password_element); - } - - // Now let's register for any text input. - // TODO(jcampan): bug #3847 merge password and form autofill so we - // traverse the form elements only once. - RegisterAutofillListeners(form, password_related_fields); } } @@ -703,40 +685,6 @@ NavigationGesture WebFrameLoaderClient::NavigationGestureForLastLoad() { NavigationGestureAuto; } -void WebFrameLoaderClient::RegisterAutofillListeners( - WebCore::HTMLFormElement* form, - const std::set<std::wstring>& excluded_fields) { - - WebViewDelegate* webview_delegate = webframe_->webview_impl()->delegate(); - if (!webview_delegate) - return; - - for (size_t i = 0; i < form->formElements.size(); i++) { - WebCore::HTMLFormControlElement* form_element = form->formElements[i]; - if (!form_element->hasLocalName(WebCore::HTMLNames::inputTag)) - continue; - - WebCore::HTMLInputElement* input_element = - static_cast<WebCore::HTMLInputElement*>(form_element); - if (!input_element->isEnabled() || !input_element->isTextField() || - input_element->isPasswordField() || !input_element->autoComplete()) { - continue; - } - - std::wstring name = webkit_glue::StringToStdWString(input_element->name()); - if (name.empty() || excluded_fields.find(name) != excluded_fields.end()) - continue; - -#if !defined(OS_MACOSX) - // FIXME on Mac - webkit_glue::FormAutocompleteListener* listener = - new webkit_glue::FormAutocompleteListener(webview_delegate); - webframe_->GetAutocompleteListener()->AddInputListener(input_element, - listener); -#endif - } -} - void WebFrameLoaderClient::dispatchDidReceiveTitle(const String& title) { WebViewImpl* webview = webframe_->webview_impl(); WebViewDelegate* d = webview->delegate(); @@ -1001,7 +949,7 @@ void WebFrameLoaderClient::dispatchWillSubmitForm(FramePolicyFunction function, PassRefPtr<FormState> form_ref) { SearchableFormData* form_data = SearchableFormData::Create(form_ref->form()); WebDocumentLoaderImpl* loader = static_cast<WebDocumentLoaderImpl*>( - webframe_->frame()->loader()->provisionalDocumentLoader()); + webframe_->frame()->loader()->provisionalDocumentLoader()); // Don't free the SearchableFormData, the loader will do that. loader->set_searchable_form_data(form_data); diff --git a/webkit/glue/webframeloaderclient_impl.h b/webkit/glue/webframeloaderclient_impl.h index 5459f93..70c124b 100644 --- a/webkit/glue/webframeloaderclient_impl.h +++ b/webkit/glue/webframeloaderclient_impl.h @@ -209,11 +209,6 @@ class WebFrameLoaderClient : public WebCore::FrameLoaderClient { // otherwise returns NavigationGestureUnknown. NavigationGesture NavigationGestureForLastLoad(); - // Registers the text input fields in the passed form for autofill, with the - // exclusion of any field whose name is contained in |excluded_fields|. - void RegisterAutofillListeners(WebCore::HTMLFormElement* form, - const std::set<std::wstring>& excluded_fields); - // The WebFrame that owns this object and manages its lifetime. Therefore, // the web frame object is guaranteed to exist. WebFrameImpl* webframe_; |