| 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 |