summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--webkit/build/glue/glue.vcproj16
-rw-r--r--webkit/glue/SConscript2
-rw-r--r--webkit/glue/autocomplete_input_listener.cc231
-rw-r--r--webkit/glue/autocomplete_input_listener.h200
-rw-r--r--webkit/glue/autocomplete_input_listener_unittest.cc454
-rw-r--r--webkit/glue/dom_operations.cc6
-rw-r--r--webkit/glue/editor_client_impl.cc121
-rw-r--r--webkit/glue/editor_client_impl.h21
-rw-r--r--webkit/glue/form_autocomplete_listener.cc35
-rw-r--r--webkit/glue/form_autocomplete_listener.h40
-rw-r--r--webkit/glue/password_autocomplete_listener.cc30
-rw-r--r--webkit/glue/password_autocomplete_listener.h38
-rw-r--r--webkit/glue/password_autocomplete_listener_unittest.cc2
-rw-r--r--webkit/glue/webframe_impl.cc30
-rw-r--r--webkit/glue/webframe_impl.h34
-rw-r--r--webkit/glue/webframeloaderclient_impl.cc64
-rw-r--r--webkit/glue/webframeloaderclient_impl.h5
-rw-r--r--webkit/tools/test_shell/keyboard_unittest.cc2
-rw-r--r--webkit/tools/test_shell/test_shell_tests.vcproj4
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>