diff options
-rw-r--r-- | webkit/build/glue/glue.vcproj | 16 | ||||
-rw-r--r-- | webkit/glue/SConscript | 2 | ||||
-rw-r--r-- | webkit/glue/autocomplete_input_listener.cc | 231 | ||||
-rw-r--r-- | webkit/glue/autocomplete_input_listener.h | 200 | ||||
-rw-r--r-- | webkit/glue/autocomplete_input_listener_unittest.cc | 454 | ||||
-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 | ||||
-rw-r--r-- | webkit/tools/test_shell/keyboard_unittest.cc | 2 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell_tests.vcproj | 4 |
19 files changed, 925 insertions, 410 deletions
diff --git a/webkit/build/glue/glue.vcproj b/webkit/build/glue/glue.vcproj index 62ca13d..5e7a052 100644 --- a/webkit/build/glue/glue.vcproj +++ b/webkit/build/glue/glue.vcproj @@ -273,6 +273,14 @@ > </File> <File + RelativePath="..\..\glue\autocomplete_input_listener.cc" + > + </File> + <File + RelativePath="..\..\glue\autocomplete_input_listener.h" + > + </File> + <File RelativePath="..\..\glue\cache_manager.cc" > </File> @@ -385,6 +393,14 @@ > </File> <File + RelativePath="..\..\glue\form_autocomplete_listener.cc" + > + </File> + <File + RelativePath="..\..\glue\form_autocomplete_listener.h" + > + </File> + <File RelativePath="..\..\glue\glue_accessibility.cc" > </File> diff --git a/webkit/glue/SConscript b/webkit/glue/SConscript index ead5570..1738548 100644 --- a/webkit/glue/SConscript +++ b/webkit/glue/SConscript @@ -20,6 +20,7 @@ if env['PLATFORM'] == 'win32': input_files = [ 'alt_404_page_resource_fetcher.cc', 'alt_error_page_resource_fetcher.cc', + 'autocomplete_input_listener.cc', 'autofill_form.cc', 'cache_manager.cc', 'chrome_client_impl.cc', @@ -36,6 +37,7 @@ input_files = [ 'editor_client_impl.cc', 'entity_map.cc', 'event_conversion.cc', + 'form_autocomplete_listener.cc', 'feed_preview.cc', 'glue_util.cc', 'glue_serialize.cc', diff --git a/webkit/glue/autocomplete_input_listener.cc b/webkit/glue/autocomplete_input_listener.cc new file mode 100644 index 0000000..0aceb02 --- /dev/null +++ b/webkit/glue/autocomplete_input_listener.cc @@ -0,0 +1,231 @@ +// 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.h b/webkit/glue/autocomplete_input_listener.h deleted file mode 100644 index b89b698..0000000 --- a/webkit/glue/autocomplete_input_listener.h +++ /dev/null @@ -1,200 +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" -#include "base/scoped_ptr.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/autocomplete_input_listener_unittest.cc b/webkit/glue/autocomplete_input_listener_unittest.cc new file mode 100644 index 0000000..970263e --- /dev/null +++ b/webkit/glue/autocomplete_input_listener_unittest.cc @@ -0,0 +1,454 @@ +// 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 +// 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 a488573..6b560c8 100644 --- a/webkit/glue/dom_operations.cc +++ b/webkit/glue/dom_operations.cc @@ -27,6 +27,10 @@ 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" @@ -445,7 +449,7 @@ void FillPasswordForm(WebView* view, static_cast<WebFrameLoaderClient*>(username_element->document()-> frame()->loader()->client()); WebFrameImpl* webframe_impl = frame_loader_client->webframe(); - webframe_impl->RegisterPasswordListener( + webframe_impl->GetAutocompleteListener()->AddInputListener( 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 27c2657..722d5fb 100644 --- a/webkit/glue/editor_client_impl.cc +++ b/webkit/glue/editor_client_impl.cc @@ -17,7 +17,6 @@ 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" @@ -25,7 +24,6 @@ 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" @@ -39,10 +37,6 @@ 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 @@ -69,11 +63,8 @@ EditorClientImpl::EditorClientImpl(WebView* web_view) : web_view_(static_cast<WebViewImpl*>(web_view)), use_editor_delegate_(false), in_redo_(false), - backspace_pressed_(false), -// Don't complain about using "this" in initializer list. -MSVC_PUSH_DISABLE_WARNING(4355) - autofill_factory_(this) { -MSVC_POP_WARNING() + preserve_(false), + pending_inline_autocompleted_element_(NULL) { } EditorClientImpl::~EditorClientImpl() { @@ -191,6 +182,10 @@ 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, @@ -205,6 +200,12 @@ 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; } @@ -241,6 +242,25 @@ 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) @@ -601,84 +621,17 @@ 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) { - 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)); + // 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; } -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); - +bool EditorClientImpl::doTextFieldCommandFromEvent(WebCore::Element*, + WebCore::KeyboardEvent*) { // 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 4d0f057..189a9af 100644 --- a/webkit/glue/editor_client_impl.h +++ b/webkit/glue/editor_client_impl.h @@ -6,7 +6,6 @@ #define WEBKIT_GLUE_EDITOR_CLIENT_IMPL_H__ #include "base/compiler_specific.h" -#include "base/task.h" #include "build/build_config.h" @@ -18,7 +17,6 @@ MSVC_POP_WARNING(); namespace WebCore { class Frame; -class HTMLInputElement; class Node; class PlatformKeyboardEvent; } @@ -105,6 +103,9 @@ 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. @@ -122,23 +123,21 @@ 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 new file mode 100644 index 0000000..e5751ca --- /dev/null +++ b/webkit/glue/form_autocomplete_listener.cc @@ -0,0 +1,35 @@ +// 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 new file mode 100644 index 0000000..1c86c02 --- /dev/null +++ b/webkit/glue/form_autocomplete_listener.h @@ -0,0 +1,40 @@ +// 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 2a96ae4..fff24f4 100644 --- a/webkit/glue/password_autocomplete_listener.cc +++ b/webkit/glue/password_autocomplete_listener.cc @@ -8,39 +8,9 @@ #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 9d74797..a2c7a7a 100644 --- a/webkit/glue/password_autocomplete_listener.h +++ b/webkit/glue/password_autocomplete_listener.h @@ -8,42 +8,13 @@ #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 "base/scoped_ptr.h" +#include "webkit/glue/autocomplete_input_listener.h" #include "webkit/glue/password_form_dom_manager.h" namespace webkit_glue { -// 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 { +class PasswordAutocompleteListener : public AutocompleteInputListener { public: PasswordAutocompleteListener(HTMLInputDelegate* username_delegate, HTMLInputDelegate* password_delegate, @@ -51,6 +22,7 @@ class PasswordAutocompleteListener { virtual ~PasswordAutocompleteListener() { } + // AutocompleteInputListener implementation. virtual void OnBlur(WebCore::HTMLInputElement* element, const std::wstring& user_input); virtual void OnInlineAutocompleteNeeded(WebCore::HTMLInputElement* element, @@ -58,8 +30,8 @@ class PasswordAutocompleteListener { 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 e4b4e79..85cfa9f 100644 --- a/webkit/glue/password_autocomplete_listener_unittest.cc +++ b/webkit/glue/password_autocomplete_listener_unittest.cc @@ -24,9 +24,11 @@ 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 4ee45f9..fa9960c 100644 --- a/webkit/glue/webframe_impl.cc +++ b/webkit/glue/webframe_impl.cc @@ -286,7 +286,8 @@ MSVC_POP_WARNING() frames_scoping_count_(-1), scoping_complete_(false), next_invalidate_after_(0), - printing_(false) { + printing_(false), + form_autocomplete_listener_(NULL) { StatsCounter(kWebFrameActiveCount).Increment(); live_object_count_++; } @@ -296,7 +297,6 @@ WebFrameImpl::~WebFrameImpl() { live_object_count_--; CancelPendingScopingEffort(); - ClearPasswordListeners(); } // WebFrame ------------------------------------------------------------------- @@ -1873,24 +1873,14 @@ int WebFrameImpl::PendingFrameUnloadEventCount() const { return frame()->eventHandler()->pendingFrameUnloadEventCount(); } -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); -} - -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; +webkit_glue::AutocompleteBodyListener* WebFrameImpl::GetAutocompleteListener() { + if (!form_autocomplete_listener_) { + form_autocomplete_listener_ = + adoptRef(new webkit_glue::AutocompleteBodyListener(frame())); } - password_listeners_.clear(); + return form_autocomplete_listener_.get(); } +void WebFrameImpl::ClearAutocompleteListener() { + form_autocomplete_listener_ = NULL; +} diff --git a/webkit/glue/webframe_impl.h b/webkit/glue/webframe_impl.h index d50762d..b3b241a 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/password_autocomplete_listener.h" +#include "webkit/glue/form_autocomplete_listener.h" #include "webkit/glue/webdatasource_impl.h" #include "webkit/glue/webframe.h" #include "webkit/glue/webframeloaderclient_impl.h" @@ -271,20 +271,14 @@ class WebFrameImpl : public WebFrame { virtual bool IsReloadAllowingStaleData() const; - // 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); + // 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(); protected: friend class WebFrameLoaderClient; @@ -446,20 +440,14 @@ 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 input fields that are interested in edit events and their associated - // listeners. - typedef HashMap<RefPtr<WebCore::HTMLInputElement>, - webkit_glue::PasswordAutocompleteListener*> PasswordListenerMap; - PasswordListenerMap password_listeners_; + // The listener responsible for showing form autocomplete suggestions. + RefPtr<webkit_glue::AutocompleteBodyListener> form_autocomplete_listener_; DISALLOW_COPY_AND_ASSIGN(WebFrameImpl); }; diff --git a/webkit/glue/webframeloaderclient_impl.cc b/webkit/glue/webframeloaderclient_impl.cc index 5983854..f311ce1 100644 --- a/webkit/glue/webframeloaderclient_impl.cc +++ b/webkit/glue/webframeloaderclient_impl.cc @@ -44,6 +44,8 @@ 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" @@ -326,10 +328,9 @@ 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 we don't hold on to our password - // listeners and their associated HTMLInputElements. - webframe_->ClearPasswordListeners(); + // A frame may be reused. This call ensures a new AutoCompleteListener will + // be created for the newly created frame. + webframe_->ClearAutocompleteListener(); // The document has now been fully loaded. // Scan for password forms to be sent to the browser @@ -350,11 +351,28 @@ 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); } } @@ -685,6 +703,40 @@ 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(); @@ -949,7 +1001,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 70c124b..5459f93 100644 --- a/webkit/glue/webframeloaderclient_impl.h +++ b/webkit/glue/webframeloaderclient_impl.h @@ -209,6 +209,11 @@ 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_; diff --git a/webkit/tools/test_shell/keyboard_unittest.cc b/webkit/tools/test_shell/keyboard_unittest.cc index 4389ab8..de98b32 100644 --- a/webkit/tools/test_shell/keyboard_unittest.cc +++ b/webkit/tools/test_shell/keyboard_unittest.cc @@ -13,8 +13,6 @@ MSVC_PUSH_WARNING_LEVEL(0); #include "KeyboardEvent.h" MSVC_POP_WARNING(); -#undef LOG - #include "webkit/glue/editor_client_impl.h" #include "webkit/glue/event_conversion.h" #include "webkit/glue/webinputevent.h" diff --git a/webkit/tools/test_shell/test_shell_tests.vcproj b/webkit/tools/test_shell/test_shell_tests.vcproj index 26bd25d..38df275 100644 --- a/webkit/tools/test_shell/test_shell_tests.vcproj +++ b/webkit/tools/test_shell/test_shell_tests.vcproj @@ -295,6 +295,10 @@ Name="tests" > <File + RelativePath="..\..\glue\autocomplete_input_listener_unittest.cc" + > + </File> + <File RelativePath="..\..\port\platform\image-decoders\bmp\BMPImageDecoder_unittest.cpp" > </File> |