OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 // | |
5 // This file provides an abstract implementation of the inline autocomplete | |
6 // infrastructure defined in autocomplete_input_listener.h. | |
7 | |
8 #include "webkit/glue/autocomplete_input_listener.h" | |
9 #include <set> | |
10 | |
11 MSVC_PUSH_WARNING_LEVEL(0); | |
12 #include "HTMLInputElement.h" | |
13 #include "HTMLFormElement.h" | |
14 #include "Document.h" | |
15 #include "Frame.h" | |
16 #include "Editor.h" | |
17 #include "EventNames.h" | |
18 #include "Event.h" | |
19 #include "HTMLNames.h" | |
20 MSVC_POP_WARNING(); | |
21 | |
22 #undef LOG | |
23 | |
24 #include "base/logging.h" | |
25 #include "webkit/glue/editor_client_impl.h" | |
26 #include "webkit/glue/glue_util.h" | |
27 | |
28 namespace webkit_glue { | |
29 | |
30 // Hack (1 of 2) for http://bugs.webkit.org/show_bug.cgi?id=16976. This bug | |
31 // causes the caret position to be set after handling input events, which | |
32 // trumps our modifications, so for now we tell the EditorClient to preserve | |
33 // whatever selection set by our code. | |
34 // TODO(timsteele): Remove this function altogether once bug is fixed. | |
35 static void PreserveSelection(WebCore::HTMLInputElement* element) { | |
36 WebCore::EditorClient* ec = | |
37 element->form()->document()->frame()->editor()->client(); | |
38 EditorClientImpl* client = static_cast<EditorClientImpl*>(ec); | |
39 client->PreserveSelection(); | |
40 } | |
41 | |
42 HTMLInputDelegate::HTMLInputDelegate(WebCore::HTMLInputElement* element) | |
43 : element_(element) { | |
44 // Reference the element for the lifetime of this delegate. | |
45 // e is NULL when testing. | |
46 if (element_) | |
47 element_->ref(); | |
48 } | |
49 | |
50 HTMLInputDelegate::~HTMLInputDelegate() { | |
51 if (element_) | |
52 element_->deref(); | |
53 } | |
54 | |
55 void HTMLInputDelegate::SetValue(const std::wstring& value) { | |
56 element_->setValue(StdWStringToString(value)); | |
57 } | |
58 | |
59 void HTMLInputDelegate::SetSelectionRange(size_t start, size_t end) { | |
60 element_->setSelectionRange(start, end); | |
61 // Hack, see comments for PreserveSelection(). | |
62 PreserveSelection(element_); | |
63 } | |
64 | |
65 void HTMLInputDelegate::OnFinishedAutocompleting() { | |
66 // This sets the input element to an autofilled state which will result in it | |
67 // having a yellow background. | |
68 element_->setAutofilled(true); | |
69 // Notify any changeEvent listeners. | |
70 element_->onChange(); | |
71 } | |
72 | |
73 AutocompleteBodyListener::AutocompleteBodyListener(WebCore::Frame* frame) { | |
74 WebCore::HTMLElement* body = frame->document()->body(); | |
75 body->addEventListener(WebCore::eventNames().DOMFocusOutEvent, this, false); | |
76 body->addEventListener(WebCore::eventNames().inputEvent, this, false); | |
77 // Attaching to the WebCore body element effectively transfers ownership of | |
78 // the listener object. When WebCore is tearing down the document, any | |
79 // attached listeners are destroyed. | |
80 // See Document::removeAllEventListenersFromAllNodes which is called by | |
81 // FrameLoader::stopLoading. Also, there is no need for matching calls to | |
82 // removeEventListener because the simplest and most convienient thing to do | |
83 // for autocompletion is to stop listening once the element is destroyed. | |
84 } | |
85 | |
86 AutocompleteBodyListener::~AutocompleteBodyListener() { | |
87 // Delete the listener. Pay special attention since we may have the same | |
88 // listener registered for several elements. | |
89 std::set<AutocompleteInputListener*> to_be_deleted_; | |
90 for (InputElementInfoMap::iterator iter = elements_info_.begin(); | |
91 iter != elements_info_.end(); ++iter) { | |
92 to_be_deleted_.insert(iter->second.listener); | |
93 } | |
94 | |
95 std::set<AutocompleteInputListener*>::iterator iter; | |
96 for (iter = to_be_deleted_.begin(); iter != to_be_deleted_.end(); ++iter) | |
97 delete *iter; | |
98 elements_info_.clear(); | |
99 } | |
100 | |
101 // The following method is based on Firefox2 code in | |
102 // toolkit/components/autocomplete/src/nsAutoCompleteController.cpp | |
103 // Its license block is | |
104 // | |
105 /* ***** BEGIN LICENSE BLOCK ***** | |
106 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 | |
107 * | |
108 * The contents of this file are subject to the Mozilla Public License Version | |
109 * 1.1 (the "License"); you may not use this file except in compliance with | |
110 * the License. You may obtain a copy of the License at | |
111 * http://www.mozilla.org/MPL/ | |
112 * | |
113 * Software distributed under the License is distributed on an "AS IS" basis, | |
114 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License | |
115 * for the specific language governing rights and limitations under the | |
116 * License. | |
117 * | |
118 * The Original Code is Mozilla Communicator client code. | |
119 * | |
120 * The Initial Developer of the Original Code is | |
121 * Netscape Communications Corporation. | |
122 * Portions created by the Initial Developer are Copyright (C) 1998 | |
123 * the Initial Developer. All Rights Reserved. | |
124 * | |
125 * Contributor(s): | |
126 * Joe Hewitt <hewitt@netscape.com> (Original Author) | |
127 * Dean Tessman <dean_tessman@hotmail.com> | |
128 * Johnny Stenback <jst@mozilla.jstenback.com> | |
129 * Masayuki Nakano <masayuki@d-toybox.com> | |
130 * | |
131 * Alternatively, the contents of this file may be used under the terms of | |
132 * either the GNU General Public License Version 2 or later (the "GPL"), or | |
133 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), | |
134 * in which case the provisions of the GPL or the LGPL are applicable instead | |
135 * of those above. If you wish to allow use of your version of this file only | |
136 * under the terms of either the GPL or the LGPL, and not to allow others to | |
137 * use your version of this file under the terms of the MPL, indicate your | |
138 * decision by deleting the provisions above and replace them with the notice | |
139 * and other provisions required by the GPL or the LGPL. If you do not delete | |
140 * the provisions above, a recipient may use your version of this file under | |
141 * the terms of any one of the MPL, the GPL or the LGPL. | |
142 * | |
143 * ***** END LICENSE BLOCK ***** */ | |
144 bool AutocompleteBodyListener::ShouldInlineAutocomplete( | |
145 WebCore::HTMLInputElement* input, | |
146 const std::wstring& old_text, | |
147 const std::wstring& new_text) { | |
148 size_t prev_length = old_text.length(); | |
149 // The following are a bunch of early returns in cases we don't want to | |
150 // go through with inline autocomplete. | |
151 | |
152 // Don't bother doing AC if nothing changed. | |
153 if (new_text.length() > 0 && (new_text == old_text)) | |
154 return false; | |
155 | |
156 // Did user backspace? | |
157 if ((new_text.length() < old_text.length()) && | |
158 old_text.substr(0, new_text.length()) == new_text) { | |
159 return false; | |
160 } | |
161 | |
162 // Is search string empty? | |
163 if (new_text.empty()) | |
164 return false; | |
165 return IsCaretAtEndOfText(input, new_text.length(), prev_length); | |
166 } | |
167 | |
168 void AutocompleteBodyListener::handleEvent(WebCore::Event* event, | |
169 bool /*is_window_event*/) { | |
170 const WebCore::AtomicString& webcore_type = event->type(); | |
171 DCHECK(event->target()->toNode()); | |
172 if (!event->target()->toNode()->hasTagName(WebCore::HTMLNames::inputTag)) | |
173 return; // Not a node of interest to us. | |
174 | |
175 WebCore::HTMLInputElement* input = | |
176 static_cast<WebCore::HTMLInputElement*>(event->target()->toNode()); | |
177 InputElementInfoMap::const_iterator iter = elements_info_.find(input); | |
178 if (iter == elements_info_.end()) | |
179 return; // Not an input node we are listening to. | |
180 | |
181 InputElementInfo input_info = iter->second; | |
182 const std::wstring& user_input = StringToStdWString(input->value()); | |
183 if (webcore_type == WebCore::eventNames().DOMFocusOutEvent) { | |
184 input_info.listener->OnBlur(input, user_input); | |
185 } else if (webcore_type == WebCore::eventNames().inputEvent) { | |
186 // Perform inline autocomplete if it is safe to do so. | |
187 if (ShouldInlineAutocomplete(input, | |
188 input_info.previous_text, user_input)) { | |
189 input_info.listener->OnInlineAutocompleteNeeded(input, user_input); | |
190 } | |
191 // Update the info. | |
192 input_info.previous_text = user_input; | |
193 elements_info_[input] = input_info; | |
194 } else { | |
195 NOTREACHED() << "unexpected EventName for autocomplete listener"; | |
196 } | |
197 } | |
198 | |
199 void AutocompleteBodyListener::AddInputListener( | |
200 WebCore::HTMLInputElement* element, | |
201 AutocompleteInputListener* listener) { | |
202 DCHECK(elements_info_.find(element) == elements_info_.end()); | |
203 InputElementInfo elem_info; | |
204 elem_info.listener = listener; | |
205 elements_info_[element] = elem_info; | |
206 } | |
207 | |
208 bool AutocompleteBodyListener::IsCaretAtEndOfText( | |
209 WebCore::HTMLInputElement* element, | |
210 size_t input_length, | |
211 size_t previous_length) const { | |
212 // Hack 2 of 2 for http://bugs.webkit.org/show_bug.cgi?id=16976. | |
213 // TODO(timsteele): This check should only return early if | |
214 // !(selectionEnd == selectionStart == user_input.length()). | |
215 // However, because of webkit bug #16976 the caret is not properly moved | |
216 // until after the handlers have executed, so for now we do the following | |
217 // several checks. The first check handles the case webkit sets the End | |
218 // selection but not the Start selection correctly, and the second is for | |
219 // when webcore sets neither. This won't be perfect if the user moves the | |
220 // selection around during inline autocomplete, but for now its the | |
221 // friendliest behavior we can offer. Once the bug is fixed this method | |
222 // should no longer need the previous_length parameter. | |
223 if (((element->selectionEnd() != element->selectionStart() + 1) || | |
224 (element->selectionEnd() != static_cast<int>(input_length))) && | |
225 ((element->selectionEnd() != element->selectionStart()) || | |
226 (element->selectionEnd() != static_cast<int>(previous_length)))) { | |
227 return false; | |
228 } | |
229 return true; | |
230 } | |
231 } // webkit_glue | |
OLD | NEW |