2 * Copyright (C) 2009, 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 #include "InputHandler.h"
22 #include "BackingStore.h"
23 #include "BackingStoreClient.h"
24 #include "CSSStyleDeclaration.h"
27 #include "DOMSupport.h"
28 #include "DatePickerClient.h"
30 #include "DocumentLoader.h"
31 #include "DocumentMarkerController.h"
32 #include "FocusController.h"
34 #include "FrameView.h"
35 #include "HTMLFormElement.h"
36 #include "HTMLInputElement.h"
37 #include "HTMLNames.h"
38 #include "HTMLOptGroupElement.h"
39 #include "HTMLOptionElement.h"
40 #include "HTMLSelectElement.h"
41 #include "HTMLTextAreaElement.h"
42 #include "NotImplemented.h"
44 #include "PlatformKeyboardEvent.h"
45 #include "PluginView.h"
47 #include "RenderLayer.h"
48 #include "RenderMenuList.h"
49 #include "RenderPart.h"
50 #include "RenderText.h"
51 #include "RenderTextControl.h"
52 #include "RenderWidget.h"
53 #include "ScopePointer.h"
54 #include "SelectPopupClient.h"
55 #include "SelectionHandler.h"
56 #include "SpellChecker.h"
57 #include "TextCheckerClient.h"
58 #include "TextIterator.h"
59 #include "WebPageClient.h"
60 #include "WebPage_p.h"
61 #include "WebSettings.h"
63 #include <BlackBerryPlatformKeyboardEvent.h>
64 #include <BlackBerryPlatformLog.h>
65 #include <BlackBerryPlatformMisc.h>
66 #include <BlackBerryPlatformSettings.h>
67 #include <imf/events.h>
68 #include <sys/keycodes.h>
70 #define ENABLE_INPUT_LOG 0
71 #define ENABLE_FOCUS_LOG 0
73 static const unsigned MaxLearnTextDataSize = 500;
75 using namespace BlackBerry::Platform;
76 using namespace WebCore;
79 #define InputLog(severity, format, ...) logAlways(severity, format, ## __VA_ARGS__)
81 #define InputLog(severity, format, ...)
82 #endif // ENABLE_INPUT_LOG
85 #define FocusLog(severity, format, ...) logAlways(severity, format, ## __VA_ARGS__)
87 #define FocusLog(severity, format, ...)
88 #endif // ENABLE_FOCUS_LOG
90 namespace BlackBerry {
93 class ProcessingChangeGuard {
95 ProcessingChangeGuard(InputHandler* inputHandler)
96 : m_inputHandler(inputHandler)
97 , m_savedProcessingChange(false)
99 ASSERT(m_inputHandler);
101 m_savedProcessingChange = m_inputHandler->processingChange();
102 m_inputHandler->setProcessingChange(true);
105 ~ProcessingChangeGuard()
107 m_inputHandler->setProcessingChange(m_savedProcessingChange);
111 InputHandler* m_inputHandler;
112 bool m_savedProcessingChange;
115 InputHandler::InputHandler(WebPagePrivate* page)
117 , m_currentFocusElement(0)
118 , m_inputModeEnabled(false)
119 , m_processingChange(false)
120 , m_changingFocus(false)
121 , m_currentFocusElementType(TextEdit)
122 , m_currentFocusElementTextEditMask(DEFAULT_STYLE)
123 , m_composingTextStart(0)
124 , m_composingTextEnd(0)
125 , m_pendingKeyboardVisibilityChange(NoChange)
126 , m_delayKeyboardVisibilityChange(false)
130 InputHandler::~InputHandler()
134 static BlackBerryInputType convertInputType(const HTMLInputElement* inputElement)
136 if (inputElement->isPasswordField())
137 return InputTypePassword;
138 if (inputElement->isSearchField())
139 return InputTypeSearch;
140 if (inputElement->isEmailField())
141 return InputTypeEmail;
142 if (inputElement->isMonthControl())
143 return InputTypeMonth;
144 if (inputElement->isNumberField())
145 return InputTypeNumber;
146 if (inputElement->isTelephoneField())
147 return InputTypeTelephone;
148 if (inputElement->isURLField())
150 #if ENABLE(INPUT_TYPE_COLOR)
151 if (inputElement->isColorControl())
152 return InputTypeColor;
154 if (inputElement->isDateControl())
155 return InputTypeDate;
156 if (inputElement->isDateTimeControl())
157 return InputTypeDateTime;
158 if (inputElement->isDateTimeLocalControl())
159 return InputTypeDateTimeLocal;
160 if (inputElement->isTimeControl())
161 return InputTypeTime;
162 // FIXME: missing WEEK popup selector
163 if (DOMSupport::elementIdOrNameIndicatesEmail(inputElement))
164 return InputTypeEmail;
165 if (DOMSupport::elementIdOrNameIndicatesUrl(inputElement))
167 if (DOMSupport::elementPatternIndicatesNumber(inputElement))
168 return InputTypeNumber;
169 if (DOMSupport::elementPatternIndicatesHexadecimal(inputElement))
170 return InputTypeHexadecimal;
172 return InputTypeText;
175 static int inputStyle(BlackBerryInputType type, const Element* element)
180 case InputTypeSearch:
182 case InputTypeTextArea:
184 // Regular input mode, disable help if autocomplete is off.
186 DOMSupport::AttributeState autoCompleteState = DOMSupport::elementSupportsAutocomplete(element);
187 if (autoCompleteState == DOMSupport::Off)
188 imfMask = NO_AUTO_TEXT | NO_PREDICTION;
189 else if (autoCompleteState != DOMSupport::On
190 && DOMSupport::elementIdOrNameIndicatesNoAutocomplete(element))
191 imfMask = NO_AUTO_TEXT | NO_PREDICTION;
193 // Disable autocorrection if it's specifically off, of if it is in default mode
194 // and we have disabled auto text and prediction.
195 if (DOMSupport::elementSupportsAutocorrect(element) == DOMSupport::Off
196 || (imfMask && DOMSupport::elementSupportsAutocorrect(element) == DOMSupport::Default))
197 imfMask |= NO_AUTO_CORRECTION;
201 if ((type == InputTypeEmail || type == InputTypeURL) && autoCompleteState != DOMSupport::On)
202 return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
205 case InputTypeIsIndex:
206 case InputTypePassword:
207 case InputTypeNumber:
208 case InputTypeTelephone:
209 case InputTypeHexadecimal:
210 // Disable special handling.
211 return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
215 return DEFAULT_STYLE;
218 static VirtualKeyboardType convertStringToKeyboardType(const AtomicString& string)
220 DEFINE_STATIC_LOCAL(AtomicString, Default, ("default"));
221 DEFINE_STATIC_LOCAL(AtomicString, Url, ("url"));
222 DEFINE_STATIC_LOCAL(AtomicString, Email, ("email"));
223 DEFINE_STATIC_LOCAL(AtomicString, Password, ("password"));
224 DEFINE_STATIC_LOCAL(AtomicString, Web, ("web"));
225 DEFINE_STATIC_LOCAL(AtomicString, Number, ("number"));
226 DEFINE_STATIC_LOCAL(AtomicString, Symbol, ("symbol"));
227 DEFINE_STATIC_LOCAL(AtomicString, Phone, ("phone"));
228 DEFINE_STATIC_LOCAL(AtomicString, Pin, ("pin"));
229 DEFINE_STATIC_LOCAL(AtomicString, Hex, ("hexadecimal"));
231 if (string.isEmpty())
232 return VKBTypeNotSet;
233 if (equalIgnoringCase(string, Default))
234 return VKBTypeDefault;
235 if (equalIgnoringCase(string, Url))
237 if (equalIgnoringCase(string, Email))
239 if (equalIgnoringCase(string, Password))
240 return VKBTypePassword;
241 if (equalIgnoringCase(string, Web))
243 if (equalIgnoringCase(string, Number))
244 return VKBTypeNumPunc;
245 if (equalIgnoringCase(string, Symbol))
246 return VKBTypeSymbol;
247 if (equalIgnoringCase(string, Phone))
249 if (equalIgnoringCase(string, Pin) || equalIgnoringCase(string, Hex))
251 return VKBTypeNotSet;
254 static VirtualKeyboardType keyboardTypeAttribute(const WebCore::Element* element)
256 DEFINE_STATIC_LOCAL(QualifiedName, keyboardTypeAttr, (nullAtom, "data-blackberry-virtual-keyboard-type", nullAtom));
258 if (element->fastHasAttribute(keyboardTypeAttr)) {
259 AtomicString attributeString = element->fastGetAttribute(keyboardTypeAttr);
260 return convertStringToKeyboardType(attributeString);
263 if (element->isFormControlElement()) {
264 const HTMLFormControlElement* formElement = static_cast<const HTMLFormControlElement*>(element);
265 if (formElement->form() && formElement->form()->fastHasAttribute(keyboardTypeAttr)) {
266 AtomicString attributeString = formElement->form()->fastGetAttribute(keyboardTypeAttr);
267 return convertStringToKeyboardType(attributeString);
271 return VKBTypeNotSet;
274 static VirtualKeyboardEnterKeyType convertStringToKeyboardEnterKeyType(const AtomicString& string)
276 DEFINE_STATIC_LOCAL(AtomicString, Default, ("default"));
277 DEFINE_STATIC_LOCAL(AtomicString, Connect, ("connect"));
278 DEFINE_STATIC_LOCAL(AtomicString, Done, ("done"));
279 DEFINE_STATIC_LOCAL(AtomicString, Go, ("go"));
280 DEFINE_STATIC_LOCAL(AtomicString, Join, ("join"));
281 DEFINE_STATIC_LOCAL(AtomicString, Next, ("next"));
282 DEFINE_STATIC_LOCAL(AtomicString, Search, ("search"));
283 DEFINE_STATIC_LOCAL(AtomicString, Send, ("send"));
284 DEFINE_STATIC_LOCAL(AtomicString, Submit, ("submit"));
286 if (string.isEmpty())
287 return VKBEnterKeyNotSet;
288 if (equalIgnoringCase(string, Default))
289 return VKBEnterKeyDefault;
290 if (equalIgnoringCase(string, Connect))
291 return VKBEnterKeyConnect;
292 if (equalIgnoringCase(string, Done))
293 return VKBEnterKeyDone;
294 if (equalIgnoringCase(string, Go))
295 return VKBEnterKeyGo;
296 if (equalIgnoringCase(string, Join))
297 return VKBEnterKeyJoin;
298 if (equalIgnoringCase(string, Next))
299 return VKBEnterKeyNext;
300 if (equalIgnoringCase(string, Search))
301 return VKBEnterKeySearch;
302 if (equalIgnoringCase(string, Send))
303 return VKBEnterKeySend;
304 if (equalIgnoringCase(string, Submit))
305 return VKBEnterKeySubmit;
306 return VKBEnterKeyNotSet;
309 static VirtualKeyboardEnterKeyType keyboardEnterKeyTypeAttribute(const WebCore::Element* element)
311 DEFINE_STATIC_LOCAL(QualifiedName, keyboardEnterKeyTypeAttr, (nullAtom, "data-blackberry-virtual-keyboard-enter-key", nullAtom));
313 if (element->fastHasAttribute(keyboardEnterKeyTypeAttr)) {
314 AtomicString attributeString = element->fastGetAttribute(keyboardEnterKeyTypeAttr);
315 return convertStringToKeyboardEnterKeyType(attributeString);
318 if (element->isFormControlElement()) {
319 const HTMLFormControlElement* formElement = static_cast<const HTMLFormControlElement*>(element);
320 if (formElement->form() && formElement->form()->fastHasAttribute(keyboardEnterKeyTypeAttr)) {
321 AtomicString attributeString = formElement->form()->fastGetAttribute(keyboardEnterKeyTypeAttr);
322 return convertStringToKeyboardEnterKeyType(attributeString);
326 return VKBEnterKeyNotSet;
329 WTF::String InputHandler::elementText()
331 if (!isActiveTextEdit())
332 return WTF::String();
334 return DOMSupport::inputElementText(m_currentFocusElement.get());
337 BlackBerryInputType InputHandler::elementType(Element* element) const
339 // <isIndex> is bundled with input so we need to check the formControlName
340 // first to differentiate it from input which is essentially the same as
341 // isIndex has been deprecated.
342 if (element->formControlName() == HTMLNames::isindexTag)
343 return InputTypeIsIndex;
345 if (const HTMLInputElement* inputElement = static_cast<const HTMLInputElement*>(element->toInputElement()))
346 return convertInputType(inputElement);
348 if (element->hasTagName(HTMLNames::textareaTag))
349 return InputTypeTextArea;
351 // Default to InputTypeTextArea for content editable fields.
352 return InputTypeTextArea;
355 void InputHandler::focusedNodeChanged()
357 ASSERT(m_webPage->m_page->focusController());
358 Frame* frame = m_webPage->m_page->focusController()->focusedOrMainFrame();
359 if (!frame || !frame->document())
362 Node* node = frame->document()->focusedNode();
364 if (isActiveTextEdit() && m_currentFocusElement == node) {
365 if (!processingChange())
366 notifyClientOfKeyboardVisibilityChange(true);
370 if (node && node->isElementNode()) {
371 Element* element = static_cast<Element*>(node);
372 if (DOMSupport::isElementTypePlugin(element)) {
373 setPluginFocused(element);
377 if (DOMSupport::isTextBasedContentEditableElement(element)) {
378 // Focused node is a text based input field, textarea or content editable field.
379 setElementFocused(element);
384 if (isActiveTextEdit() && m_currentFocusElement->isContentEditable()) {
385 // This is a special handler for content editable fields. The focus node is the top most
386 // field that is content editable, however, by enabling / disabling designmode and
387 // content editable, it is possible through javascript or selection to trigger the focus node to
388 // change even while maintaining editing on the same selection point. If the focus element
389 // isn't contentEditable, but the current selection is, don't send a change notification.
391 // When processing changes selection changes occur that may reset the focus element
392 // and could potentially cause a crash as m_currentFocusElement should not be
393 // changed during processing of an EditorCommand.
394 if (processingChange())
397 Frame* frame = m_currentFocusElement->document()->frame();
400 // Test the current selection to make sure that the content in focus is still content
401 // editable. This may mean Javascript triggered a focus change by modifying the
402 // top level parent of this object's content editable state without actually modifying
403 // this particular object.
404 // Example site: html5demos.com/contentEditable - blur event triggers focus change.
405 if (frame == m_webPage->focusedOrMainFrame() && frame->selection()->start().anchorNode()
406 && frame->selection()->start().anchorNode()->isContentEditable())
410 // No valid focus element found for handling.
411 setElementUnfocused();
414 void InputHandler::setPluginFocused(Element* element)
416 ASSERT(DOMSupport::isElementTypePlugin(element));
418 if (isActiveTextEdit())
419 setElementUnfocused();
421 m_currentFocusElementType = Plugin;
422 m_currentFocusElement = element;
425 static bool convertStringToWchar(const String& string, wchar_t* dest, int destCapacity, int* destLength)
429 if (!string.length()) {
434 UErrorCode ec = U_ZERO_ERROR;
435 // wchar_t strings sent to IMF are 32 bit so casting to UChar32 is safe.
436 u_strToUTF32(reinterpret_cast<UChar32*>(dest), destCapacity, destLength, string.characters(), string.length(), &ec);
438 logAlways(LogLevelCritical, "InputHandler::convertStringToWchar Error converting string ec (%d).", ec);
445 static bool convertStringToWcharVector(const String& string, WTF::Vector<wchar_t>& wcharString)
447 ASSERT(wcharString.isEmpty());
449 int length = string.length();
453 if (!wcharString.tryReserveCapacity(length + 1)) {
454 logAlways(LogLevelCritical, "InputHandler::convertStringToWcharVector Cannot allocate memory for string.\n");
459 if (!convertStringToWchar(string, wcharString.data(), length + 1, &destLength))
462 wcharString.resize(destLength);
466 static String convertSpannableStringToString(spannable_string_t* src)
468 if (!src || !src->str || !src->length)
471 WTF::Vector<UChar> dest;
472 int destCapacity = (src->length * 2) + 1;
473 if (!dest.tryReserveCapacity(destCapacity)) {
474 logAlways(LogLevelCritical, "InputHandler::convertSpannableStringToString Cannot allocate memory for string.\n");
479 UErrorCode ec = U_ZERO_ERROR;
480 // wchar_t strings sent from IMF are 32 bit so casting to UChar32 is safe.
481 u_strFromUTF32(dest.data(), destCapacity, &destLength, reinterpret_cast<UChar32*>(src->str), src->length, &ec);
483 logAlways(LogLevelCritical, "InputHandler::convertSpannableStringToString Error converting string ec (%d).", ec);
486 dest.resize(destLength);
487 return String(dest.data(), destLength);
490 void InputHandler::sendLearnTextDetails(const WTF::String& string)
492 Vector<wchar_t> wcharString;
493 if (!convertStringToWcharVector(string, wcharString) || wcharString.isEmpty())
496 m_webPage->m_client->inputLearnText(wcharString.data(), wcharString.size());
499 void InputHandler::learnText()
501 if (!isActiveTextEdit())
504 // Do not send (or calculate) the text when the field is NO_PREDICTION or NO_AUTO_TEXT.
505 if (m_currentFocusElementTextEditMask & NO_PREDICTION || m_currentFocusElementTextEditMask & NO_AUTO_TEXT)
508 String textInField(elementText());
509 textInField = textInField.substring(std::max(0, static_cast<int>(textInField.length() - MaxLearnTextDataSize)), textInField.length());
510 textInField.remove(0, textInField.find(" "));
512 // Build up the 500 character strings in word chunks.
513 // Spec says 1000, but memory corruption has been observed.
514 ASSERT(textInField.length() <= MaxLearnTextDataSize);
516 if (textInField.isEmpty())
519 InputLog(LogLevelInfo, "InputHandler::learnText '%s'", textInField.latin1().data());
520 sendLearnTextDetails(textInField);
523 void InputHandler::requestCheckingOfString(PassRefPtr<WebCore::TextCheckingRequest> textCheckingRequest)
525 RefPtr<WebCore::TextCheckingRequest> request = textCheckingRequest;
527 int32_t sequenceId = request->sequence();
528 int requestLength = request->text().length();
529 if (!requestLength /* || requestLength > maxSpellCheckStringLength */) {
530 spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
534 wchar_t* checkingString = (wchar_t*)malloc(sizeof(wchar_t) * (requestLength + 1));
535 if (!checkingString) {
536 BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelCritical, "InputHandler::requestCheckingOfString Cannot allocate memory for string.");
537 spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
541 int stringLength = 0;
542 if (!convertStringToWchar(request->text(), checkingString, requestLength + 1, &stringLength)) {
543 BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelCritical, "InputHandler::requestCheckingOfString Failed to convert String to wchar type.");
544 free(checkingString);
545 spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
549 int32_t transactionId = m_webPage->m_client->checkSpellingOfStringAsync(checkingString, stringLength);
550 free(checkingString);
552 // If the call to the input service did not go through, then cancel the request so we don't block endlessly.
553 // This should still take transactionId as a parameter to maintain the same behavior as if InputMethodSupport
554 // were to cancel a request during processing.
555 if (transactionId == -1) { // Error before sending request to input service.
556 spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
560 // map sequenceId to transactionId.
561 m_sequenceMap[transactionId] = sequenceId;
564 int32_t InputHandler::convertTransactionIdToSequenceId(int32_t transactionId)
566 std::map<int32_t, int32_t>::iterator it = m_sequenceMap.find(transactionId);
568 if (it == m_sequenceMap.end())
571 int32_t sequenceId = it->second;
572 // We only convert this value when we have received its response, so its safe to remove it from the map.
573 m_sequenceMap.erase(it);
578 void InputHandler::spellCheckingRequestProcessed(int32_t transactionId, spannable_string_t* spannableString)
580 if (!spannableString) {
581 spellCheckingRequestCancelled(transactionId, false /* isSequenceId */);
585 Vector<TextCheckingResult> results;
587 // Convert the spannableString to TextCheckingResult then append to results vector.
589 TextCheckingResult textCheckingResult;
590 textCheckingResult.type = TextCheckingTypeSpelling;
591 textCheckingResult.replacement = replacement;
592 textCheckingResult.location = 0;
593 textCheckingResult.length = 0;
595 span_t* span = spannableString->spans;
596 for (unsigned int i = 0; i < spannableString->spans_count; i++) {
599 if (span->end < span->start) {
600 spellCheckingRequestCancelled(transactionId, false /* isSequenceId */);
603 if (span->attributes_mask & MISSPELLED_WORD_ATTRIB) {
604 textCheckingResult.location = span->start;
605 // The end point includes the character that it is before. Ie, 0, 0
606 // applies to the first character as the end point includes the character
607 // at the position. This means the endPosition is always +1.
608 textCheckingResult.length = span->end - span->start + 1;
609 results.append(textCheckingResult);
614 // transactionId here is for use with the input service. We need to translate this to sequenceId used with SpellChecker.
615 int32_t sequenceId = convertTransactionIdToSequenceId(transactionId);
617 SpellChecker* sp = getSpellChecker();
618 if (!sp || !sequenceId) {
619 InputLog(LogLevelWarn, "InputHandler::spellCheckingRequestProcessed failed to process the request with sequenceId %d", sequenceId);
620 spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
623 sp->didCheckSucceeded(sequenceId, results);
626 void InputHandler::spellCheckingRequestCancelled(int32_t id, bool isSequenceId)
628 int32_t sequenceId = isSequenceId ? id : convertTransactionIdToSequenceId(id);
629 SpellChecker* sp = getSpellChecker();
631 InputLog(LogLevelWarn, "InputHandler::spellCheckingRequestCancelled failed to cancel the request with sequenceId %d", sequenceId);
634 sp->didCheckCanceled(sequenceId);
637 SpellChecker* InputHandler::getSpellChecker()
639 if (Frame* frame = m_currentFocusElement->document()->frame())
640 if (Editor* editor = frame->editor())
641 return editor->spellChecker();
646 void InputHandler::setElementUnfocused(bool refocusOccuring)
648 if (isActiveTextEdit()) {
649 FocusLog(LogLevelInfo, "InputHandler::setElementUnfocused");
651 // Pass any text into the field to IMF to learn.
654 // End any composition that is in progress.
657 // Only hide the keyboard if we aren't refocusing on a new input field.
658 if (!refocusOccuring)
659 notifyClientOfKeyboardVisibilityChange(false);
661 m_webPage->m_client->inputFocusLost();
662 m_webPage->m_selectionHandler->selectionPositionChanged();
664 // If the frame selection isn't focused, focus it.
665 if (!m_currentFocusElement->document()->frame()->selection()->isFocused())
666 m_currentFocusElement->document()->frame()->selection()->setFocused(true);
669 // Clear the node details.
670 m_currentFocusElement = 0;
671 m_currentFocusElementType = TextEdit;
674 bool InputHandler::isInputModeEnabled() const
676 // Input mode is enabled when set, or when dump render tree or always show keyboard setting is enabled.
677 return m_inputModeEnabled || m_webPage->m_dumpRenderTree || Platform::Settings::instance()->alwaysShowKeyboardOnFocus();
680 void InputHandler::setInputModeEnabled(bool active)
682 FocusLog(LogLevelInfo, "InputHandler::setInputModeEnabled '%s', override is '%s'"
683 , active ? "true" : "false"
684 , m_webPage->m_dumpRenderTree || Platform::Settings::instance()->alwaysShowKeyboardOnFocus() ? "true" : "false");
686 m_inputModeEnabled = active;
688 // If the frame selection isn't focused, focus it.
689 if (isInputModeEnabled() && isActiveTextEdit() && !m_currentFocusElement->document()->frame()->selection()->isFocused())
690 m_currentFocusElement->document()->frame()->selection()->setFocused(true);
693 void InputHandler::setElementFocused(Element* element)
695 ASSERT(DOMSupport::isTextBasedContentEditableElement(element));
696 ASSERT(element->document() && element->document()->frame());
698 if (element->document()->frame()->selection()->isFocused() != isInputModeEnabled())
699 element->document()->frame()->selection()->setFocused(isInputModeEnabled());
701 // Clear the existing focus node details.
702 setElementUnfocused(true /*refocusOccuring*/);
704 // Mark this element as active and add to frame set.
705 m_currentFocusElement = element;
706 m_currentFocusElementType = TextEdit;
708 // Send details to the client about this element.
709 BlackBerryInputType type = elementType(element);
710 m_currentFocusElementTextEditMask = inputStyle(type, element);
712 VirtualKeyboardType keyboardType = keyboardTypeAttribute(element);
713 VirtualKeyboardEnterKeyType enterKeyType = keyboardEnterKeyTypeAttribute(element);
715 FocusLog(LogLevelInfo, "InputHandler::setElementFocused, Type=%d, Style=%d, Keyboard Type=%d, Enter Key=%d", type, m_currentFocusElementTextEditMask, keyboardType, enterKeyType);
716 m_webPage->m_client->inputFocusGained(type, m_currentFocusElementTextEditMask, keyboardType, enterKeyType);
718 handleInputLocaleChanged(m_webPage->m_webSettings->isWritingDirectionRTL());
720 if (!m_delayKeyboardVisibilityChange)
721 notifyClientOfKeyboardVisibilityChange(true);
724 bool InputHandler::openDatePopup(HTMLInputElement* element, BlackBerryInputType type)
726 if (!element || element->disabled() || !DOMSupport::isDateTimeInputField(element))
729 if (isActiveTextEdit())
730 clearCurrentFocusElement();
733 case BlackBerry::Platform::InputTypeDate:
734 case BlackBerry::Platform::InputTypeTime:
735 case BlackBerry::Platform::InputTypeDateTime:
736 case BlackBerry::Platform::InputTypeDateTimeLocal: {
737 // Check if popup already exists, close it if does.
738 m_webPage->m_page->chrome()->client()->closePagePopup(0);
739 String value = element->value();
740 String min = element->getAttribute(HTMLNames::minAttr).string();
741 String max = element->getAttribute(HTMLNames::maxAttr).string();
742 double step = element->getAttribute(HTMLNames::stepAttr).toDouble();
744 DatePickerClient* client = new DatePickerClient(type, value, min, max, step, m_webPage, element);
745 // Fail to create HTML popup, use the old path
746 if (!m_webPage->m_page->chrome()->client()->openPagePopup(client, WebCore::IntRect()))
747 m_webPage->m_client->openDateTimePopup(type, value, min, max, step);
751 default: // Other types not supported
756 bool InputHandler::openColorPopup(HTMLInputElement* element)
758 if (!element || element->disabled() || !DOMSupport::isColorInputField(element))
761 if (isActiveTextEdit())
762 clearCurrentFocusElement();
764 m_currentFocusElement = element;
765 m_currentFocusElementType = TextPopup;
767 m_webPage->m_client->openColorPopup(element->value());
771 void InputHandler::setInputValue(const WTF::String& value)
773 if (!isActiveTextPopup())
776 HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(m_currentFocusElement.get());
777 inputElement->setValue(value);
778 clearCurrentFocusElement();
781 void InputHandler::nodeTextChanged(const Node* node)
783 if (processingChange() || !node)
786 if (node != m_currentFocusElement)
789 InputLog(LogLevelInfo, "InputHandler::nodeTextChanged");
791 m_webPage->m_client->inputTextChanged();
793 // Remove the attributed text markers as the previous call triggered an end to
795 removeAttributedTextMarker();
798 WebCore::IntRect InputHandler::boundingBoxForInputField()
800 if (!isActiveTextEdit())
801 return WebCore::IntRect();
803 if (!m_currentFocusElement->renderer())
804 return WebCore::IntRect();
806 return m_currentFocusElement->renderer()->absoluteBoundingBoxRect();
809 void InputHandler::ensureFocusTextElementVisible(CaretScrollType scrollType)
811 if (!isActiveTextEdit() || !isInputModeEnabled() || !m_currentFocusElement->document())
814 if (!Platform::Settings::instance()->allowCenterScrollAdjustmentForInputFields() && scrollType != EdgeIfNeeded)
817 Frame* elementFrame = m_currentFocusElement->document()->frame();
821 Frame* mainFrame = m_webPage->mainFrame();
825 FrameView* mainFrameView = mainFrame->view();
829 WebCore::IntRect selectionFocusRect;
830 switch (elementFrame->selection()->selectionType()) {
831 case VisibleSelection::CaretSelection:
832 selectionFocusRect = elementFrame->selection()->absoluteCaretBounds();
834 case VisibleSelection::RangeSelection: {
835 Position selectionPosition;
836 if (m_webPage->m_selectionHandler->lastUpdatedEndPointIsValid())
837 selectionPosition = elementFrame->selection()->end();
839 selectionPosition = elementFrame->selection()->start();
840 selectionFocusRect = VisiblePosition(selectionPosition).absoluteCaretBounds();
843 case VisibleSelection::NoSelection:
847 int fontHeight = selectionFocusRect.height();
849 if (elementFrame != mainFrame) { // Element is in a subframe.
850 // Remove any scroll offset within the subframe to get the point relative to the main frame.
851 selectionFocusRect.move(-elementFrame->view()->scrollPosition().x(), -elementFrame->view()->scrollPosition().y());
853 // Adjust the selection rect based on the frame offset in relation to the main frame if it's a subframe.
854 if (elementFrame->ownerRenderer()) {
855 WebCore::IntPoint frameOffset = elementFrame->ownerRenderer()->absoluteContentBox().location();
856 selectionFocusRect.move(frameOffset.x(), frameOffset.y());
860 Position start = elementFrame->selection()->start();
861 if (start.anchorNode() && start.anchorNode()->renderer()) {
862 if (RenderLayer* layer = start.anchorNode()->renderer()->enclosingLayer()) {
863 WebCore::IntRect actualScreenRect = WebCore::IntRect(mainFrameView->scrollPosition(), m_webPage->actualVisibleSize());
864 ScrollAlignment horizontalScrollAlignment = ScrollAlignment::alignToEdgeIfNeeded;
865 ScrollAlignment verticalScrollAlignment = ScrollAlignment::alignToEdgeIfNeeded;
867 if (scrollType != EdgeIfNeeded) {
868 // Align the selection rect if possible so that we show the field's
869 // outline if the caret is at the edge of the field.
870 if (RenderObject* focusedRenderer = m_currentFocusElement->renderer()) {
871 WebCore::IntRect nodeOutlineBounds = focusedRenderer->absoluteOutlineBounds();
872 WebCore::IntRect caretAtEdgeRect = rectForCaret(0);
873 int paddingX = abs(caretAtEdgeRect.x() - nodeOutlineBounds.x());
874 int paddingY = abs(caretAtEdgeRect.y() - nodeOutlineBounds.y());
876 if (selectionFocusRect.x() - paddingX == nodeOutlineBounds.x())
877 selectionFocusRect.setX(nodeOutlineBounds.x());
878 else if (selectionFocusRect.maxX() + paddingX == nodeOutlineBounds.maxX())
879 selectionFocusRect.setX(nodeOutlineBounds.maxX() - selectionFocusRect.width());
880 if (selectionFocusRect.y() - paddingY == nodeOutlineBounds.y())
881 selectionFocusRect.setY(nodeOutlineBounds.y() - selectionFocusRect.height());
882 else if (selectionFocusRect.maxY() + paddingY == nodeOutlineBounds.maxY())
883 selectionFocusRect.setY(nodeOutlineBounds.maxY() - selectionFocusRect.height());
885 // If the editing point is on the left hand side of the screen when the node's
886 // rect is edge aligned, edge align the node rect.
887 if (selectionFocusRect.x() - caretAtEdgeRect.x() < actualScreenRect.width() / 2)
888 selectionFocusRect.setX(nodeOutlineBounds.x());
890 horizontalScrollAlignment = ScrollAlignment::alignCenterIfNeeded;
893 verticalScrollAlignment = (scrollType == CenterAlways) ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded;
896 // Pad the rect to improve the visual appearance.
897 // Padding must be large enough to expose the selection / FCC should they exist. Dragging the handle offscreen and releasing
898 // will not trigger an automatic scroll. Using a padding of 40 will fully exposing the width of the current handle and half of
899 // the height making it usable.
900 // FIXME: This will need to be updated when the graphics change.
901 // FIXME: The value of 40 should be calculated as a unit of measure using Graphics::Screen::primaryScreen()->heightInMMToPixels
902 // using a relative value to the size of the handle. We should also consider expanding different amounts horizontally vs vertically.
903 selectionFocusRect.inflate(40 /* padding in pixels */);
904 WebCore::IntRect revealRect = layer->getRectToExpose(actualScreenRect, selectionFocusRect,
905 horizontalScrollAlignment,
906 verticalScrollAlignment);
908 mainFrameView->setConstrainsScrollingToContentEdge(false);
909 // In order to adjust the scroll position to ensure the focused input field is visible,
910 // we allow overscrolling. However this overscroll has to be strictly allowed towards the
911 // bottom of the page on the y axis only, where the virtual keyboard pops up from.
912 WebCore::IntPoint scrollLocation = revealRect.location();
913 scrollLocation.clampNegativeToZero();
914 WebCore::IntPoint maximumScrollPosition = WebCore::IntPoint(mainFrameView->contentsWidth() - actualScreenRect.width(), mainFrameView->contentsHeight() - actualScreenRect.height());
915 scrollLocation = scrollLocation.shrunkTo(maximumScrollPosition);
916 mainFrameView->setScrollPosition(scrollLocation);
917 mainFrameView->setConstrainsScrollingToContentEdge(true);
921 // If the text is too small, zoom in to make it a minimum size.
922 static const int s_minimumTextHeightInPixels = 6;
923 if (fontHeight && fontHeight < s_minimumTextHeightInPixels)
924 m_webPage->zoomAboutPoint(s_minimumTextHeightInPixels / fontHeight, m_webPage->centerOfVisibleContentsRect());
927 void InputHandler::ensureFocusPluginElementVisible()
929 if (!isActivePlugin() || !m_currentFocusElement->document())
932 Frame* elementFrame = m_currentFocusElement->document()->frame();
936 Frame* mainFrame = m_webPage->mainFrame();
940 FrameView* mainFrameView = mainFrame->view();
944 WebCore::IntRect selectionFocusRect;
946 RenderWidget* renderWidget = static_cast<RenderWidget*>(m_currentFocusElement->renderer());
948 PluginView* pluginView = static_cast<PluginView*>(renderWidget->widget());
951 selectionFocusRect = pluginView->ensureVisibleRect();
954 if (selectionFocusRect.isEmpty())
957 // FIXME: We may need to scroll the subframe (recursively) in the future. Revisit this...
958 if (elementFrame != mainFrame) { // Element is in a subframe.
959 // Remove any scroll offset within the subframe to get the point relative to the main frame.
960 selectionFocusRect.move(-elementFrame->view()->scrollPosition().x(), -elementFrame->view()->scrollPosition().y());
962 // Adjust the selection rect based on the frame offset in relation to the main frame if it's a subframe.
963 if (elementFrame->ownerRenderer()) {
964 WebCore::IntPoint frameOffset = elementFrame->ownerRenderer()->absoluteContentBox().location();
965 selectionFocusRect.move(frameOffset.x(), frameOffset.y());
969 WebCore::IntRect actualScreenRect = WebCore::IntRect(mainFrameView->scrollPosition(), m_webPage->actualVisibleSize());
970 if (actualScreenRect.contains(selectionFocusRect))
973 // Calculate a point such that the center of the requested rectangle
974 // is at the center of the screen. FIXME: If the element was partially on screen
975 // we might want to just bring the offscreen portion into view, someone needs
976 // to decide if that's the behavior we want or not.
977 WebCore::IntPoint pos(selectionFocusRect.center().x() - actualScreenRect.width() / 2,
978 selectionFocusRect.center().y() - actualScreenRect.height() / 2);
980 mainFrameView->setScrollPosition(pos);
983 void InputHandler::ensureFocusElementVisible(bool centerInView)
985 if (isActivePlugin())
986 ensureFocusPluginElementVisible();
988 ensureFocusTextElementVisible(centerInView ? CenterAlways : CenterIfNeeded);
991 void InputHandler::frameUnloaded(const Frame* frame)
993 if (!isActiveTextEdit())
996 if (m_currentFocusElement->document()->frame() != frame)
999 FocusLog(LogLevelInfo, "InputHandler::frameUnloaded");
1001 setElementUnfocused(false /*refocusOccuring*/);
1004 void InputHandler::setDelayKeyboardVisibilityChange(bool value)
1006 m_delayKeyboardVisibilityChange = value;
1007 m_pendingKeyboardVisibilityChange = NoChange;
1010 void InputHandler::processPendingKeyboardVisibilityChange()
1012 if (!m_delayKeyboardVisibilityChange) {
1013 ASSERT(m_pendingKeyboardVisibilityChange == NoChange);
1017 m_delayKeyboardVisibilityChange = false;
1019 if (m_pendingKeyboardVisibilityChange == NoChange)
1022 notifyClientOfKeyboardVisibilityChange(m_pendingKeyboardVisibilityChange == Visible);
1023 m_pendingKeyboardVisibilityChange = NoChange;
1026 void InputHandler::notifyClientOfKeyboardVisibilityChange(bool visible)
1028 // If we aren't ready for input, keyboard changes should be ignored.
1029 if (!isInputModeEnabled() && visible)
1032 if (!m_delayKeyboardVisibilityChange) {
1033 m_webPage->showVirtualKeyboard(visible);
1037 m_pendingKeyboardVisibilityChange = visible ? Visible : NotVisible;
1040 bool InputHandler::selectionAtStartOfElement()
1042 if (!isActiveTextEdit())
1045 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1047 if (!selectionStart())
1053 bool InputHandler::selectionAtEndOfElement()
1055 if (!isActiveTextEdit())
1058 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1060 return selectionStart() == static_cast<int>(elementText().length());
1063 int InputHandler::selectionStart() const
1065 return selectionPosition(true);
1068 int InputHandler::selectionEnd() const
1070 return selectionPosition(false);
1073 int InputHandler::selectionPosition(bool start) const
1075 if (!m_currentFocusElement->document() || !m_currentFocusElement->document()->frame())
1078 if (HTMLTextFormControlElement* controlElement = DOMSupport::toTextControlElement(m_currentFocusElement.get()))
1079 return start ? controlElement->selectionStart() : controlElement->selectionEnd();
1081 FrameSelection caretSelection;
1082 caretSelection.setSelection(m_currentFocusElement->document()->frame()->selection()->selection());
1083 RefPtr<Range> rangeSelection = caretSelection.selection().toNormalizedRange();
1084 if (!rangeSelection)
1087 int selectionPointInNode = start ? rangeSelection->startOffset() : rangeSelection->endOffset();
1088 Node* containerNode = start ? rangeSelection->startContainer() : rangeSelection->endContainer();
1091 RefPtr<Range> rangeForNode = rangeOfContents(m_currentFocusElement.get());
1092 rangeForNode->setEnd(containerNode, selectionPointInNode, ec);
1095 return TextIterator::rangeLength(rangeForNode.get());
1098 void InputHandler::selectionChanged()
1100 // This method can get called during WebPage shutdown process.
1101 // If that is the case, just bail out since the client is not
1102 // in a safe state of trust to request anything else from it.
1103 if (!m_webPage->m_mainFrame)
1106 if (!isActiveTextEdit())
1109 if (processingChange())
1112 // Scroll the field if necessary. This must be done even if we are processing
1113 // a change as the text change may have moved the caret. IMF doesn't require
1114 // the update, but the user needs to see the caret.
1115 ensureFocusTextElementVisible(EdgeIfNeeded);
1117 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1119 int newSelectionStart = selectionStart();
1120 int newSelectionEnd = selectionEnd();
1122 InputLog(LogLevelInfo, "InputHandler::selectionChanged selectionStart=%u, selectionEnd=%u", newSelectionStart, newSelectionEnd);
1124 m_webPage->m_client->inputSelectionChanged(newSelectionStart, newSelectionEnd);
1126 // Remove the attributed text markers as the previous call triggered an end to
1128 removeAttributedTextMarker();
1131 bool InputHandler::setCursorPosition(int location)
1133 return setSelection(location, location);
1136 bool InputHandler::setSelection(int start, int end, bool changeIsPartOfComposition)
1138 if (!isActiveTextEdit())
1141 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1143 ProcessingChangeGuard guard(this);
1145 VisibleSelection newSelection = DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), start, end);
1146 m_currentFocusElement->document()->frame()->selection()->setSelection(newSelection, changeIsPartOfComposition ? 0 : FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle);
1148 InputLog(LogLevelInfo, "InputHandler::setSelection selectionStart=%u, selectionEnd=%u", start, end);
1150 return start == selectionStart() && end == selectionEnd();
1153 WebCore::IntRect InputHandler::rectForCaret(int index)
1155 if (!isActiveTextEdit())
1156 return WebCore::IntRect();
1158 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1160 if (index < 0 || index > static_cast<int>(elementText().length())) {
1162 return WebCore::IntRect();
1165 FrameSelection caretSelection;
1166 caretSelection.setSelection(DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), index, index).visibleStart());
1167 caretSelection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
1168 return caretSelection.selection().visibleStart().absoluteCaretBounds();
1171 void InputHandler::cancelSelection()
1173 if (!isActiveTextEdit())
1176 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1178 int selectionStartPosition = selectionStart();
1179 ProcessingChangeGuard guard(this);
1180 setCursorPosition(selectionStartPosition);
1183 bool InputHandler::handleKeyboardInput(const Platform::KeyboardEvent& keyboardEvent, bool changeIsPartOfComposition)
1185 InputLog(LogLevelInfo, "InputHandler::handleKeyboardInput received character=%lc, type=%d", keyboardEvent.character(), keyboardEvent.type());
1187 // Enable input mode if we are processing a key event.
1188 setInputModeEnabled();
1190 // If we aren't specifically part of a composition, fail, IMF should never send key input
1191 // while composing text. If IMF has failed, we should have already finished the
1192 // composition manually.
1193 if (!changeIsPartOfComposition && compositionActive())
1196 ProcessingChangeGuard guard(this);
1198 unsigned adjustedModifiers = keyboardEvent.modifiers();
1199 if (WTF::isASCIIUpper(keyboardEvent.character()))
1200 adjustedModifiers |= KEYMOD_SHIFT;
1202 ASSERT(m_webPage->m_page->focusController());
1203 bool keyboardEventHandled = false;
1204 if (Frame* focusedFrame = m_webPage->m_page->focusController()->focusedFrame()) {
1205 bool isKeyChar = keyboardEvent.type() == Platform::KeyboardEvent::KeyChar;
1206 Platform::KeyboardEvent::Type type = keyboardEvent.type();
1208 // If this is a KeyChar type then we handle it as a keydown followed by a key up.
1210 type = Platform::KeyboardEvent::KeyDown;
1212 Platform::KeyboardEvent adjustedKeyboardEvent(keyboardEvent.character(), type, adjustedModifiers);
1213 keyboardEventHandled = focusedFrame->eventHandler()->keyEvent(PlatformKeyboardEvent(adjustedKeyboardEvent));
1216 type = Platform::KeyboardEvent::KeyUp;
1217 adjustedKeyboardEvent = Platform::KeyboardEvent(keyboardEvent.character(), type, adjustedModifiers);
1218 keyboardEventHandled = focusedFrame->eventHandler()->keyEvent(PlatformKeyboardEvent(adjustedKeyboardEvent)) || keyboardEventHandled;
1221 if (!changeIsPartOfComposition && type == Platform::KeyboardEvent::KeyUp)
1222 ensureFocusTextElementVisible(EdgeIfNeeded);
1224 return keyboardEventHandled;
1227 bool InputHandler::deleteSelection()
1229 if (!isActiveTextEdit())
1232 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1233 Frame* frame = m_currentFocusElement->document()->frame();
1235 if (frame->selection()->selectionType() != VisibleSelection::RangeSelection)
1238 ASSERT(frame->editor());
1239 return frame->editor()->command("DeleteBackward").execute();
1242 void InputHandler::insertText(const WTF::String& string)
1244 if (!isActiveTextEdit())
1247 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->editor());
1248 Editor* editor = m_currentFocusElement->document()->frame()->editor();
1250 editor->command("InsertText").execute(string);
1253 void InputHandler::clearField()
1255 if (!isActiveTextEdit())
1258 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->editor());
1259 Editor* editor = m_currentFocusElement->document()->frame()->editor();
1261 editor->command("SelectAll").execute();
1262 editor->command("DeleteBackward").execute();
1265 bool InputHandler::executeTextEditCommand(const WTF::String& commandName)
1267 ASSERT(m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->editor());
1268 Editor* editor = m_webPage->focusedOrMainFrame()->editor();
1270 return editor->command(commandName).execute();
1273 void InputHandler::cut()
1275 executeTextEditCommand("Cut");
1278 void InputHandler::copy()
1280 executeTextEditCommand("Copy");
1283 void InputHandler::paste()
1285 executeTextEditCommand("Paste");
1288 void InputHandler::selectAll()
1290 executeTextEditCommand("SelectAll");
1293 void InputHandler::addAttributedTextMarker(int start, int end, const AttributeTextStyle& style)
1295 if ((end - start) < 1 || end > static_cast<int>(elementText().length()))
1298 RefPtr<Range> markerRange = DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), start, end).toNormalizedRange();
1299 m_currentFocusElement->document()->markers()->addMarker(markerRange.get(), DocumentMarker::AttributeText, WTF::String("Input Marker"), style);
1302 void InputHandler::removeAttributedTextMarker()
1304 // Remove all attribute text markers.
1305 if (m_currentFocusElement && m_currentFocusElement->document())
1306 m_currentFocusElement->document()->markers()->removeMarkers(DocumentMarker::AttributeText);
1308 m_composingTextStart = 0;
1309 m_composingTextEnd = 0;
1312 void InputHandler::handleInputLocaleChanged(bool isRTL)
1314 if (!isActiveTextEdit())
1317 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1318 RenderObject* renderer = m_currentFocusElement->renderer();
1322 Editor* editor = m_currentFocusElement->document()->frame()->editor();
1324 if ((renderer->style()->direction() == RTL) != isRTL)
1325 editor->setBaseWritingDirection(isRTL ? RightToLeftWritingDirection : LeftToRightWritingDirection);
1328 void InputHandler::clearCurrentFocusElement()
1330 if (m_currentFocusElement)
1331 m_currentFocusElement->blur();
1334 bool InputHandler::willOpenPopupForNode(Node* node)
1336 // This method must be kept synchronized with InputHandler::didNodeOpenPopup.
1340 ASSERT(!node->isInShadowTree());
1342 if (node->hasTagName(HTMLNames::selectTag) || node->hasTagName(HTMLNames::optionTag)) {
1343 // We open list popups for options and selects.
1347 if (node->isElementNode()) {
1348 Element* element = static_cast<Element*>(node);
1349 if (DOMSupport::isPopupInputField(element))
1356 bool InputHandler::didNodeOpenPopup(Node* node)
1358 // This method must be kept synchronized with InputHandler::willOpenPopupForNode.
1362 ASSERT(!node->isInShadowTree());
1364 if (node->hasTagName(HTMLNames::selectTag))
1365 return openSelectPopup(static_cast<HTMLSelectElement*>(node));
1367 if (node->hasTagName(HTMLNames::optionTag)) {
1368 HTMLOptionElement* optionElement = static_cast<HTMLOptionElement*>(node);
1369 return openSelectPopup(optionElement->ownerSelectElement());
1372 if (HTMLInputElement* element = node->toInputElement()) {
1373 if (DOMSupport::isDateTimeInputField(element))
1374 return openDatePopup(element, elementType(element));
1376 if (DOMSupport::isColorInputField(element))
1377 return openColorPopup(element);
1382 bool InputHandler::openSelectPopup(HTMLSelectElement* select)
1384 if (!select || select->disabled())
1387 // If there's no view, do nothing and return.
1388 if (!select->document()->view())
1391 if (isActiveTextEdit())
1392 clearCurrentFocusElement();
1394 m_currentFocusElement = select;
1395 m_currentFocusElementType = SelectPopup;
1397 const WTF::Vector<HTMLElement*>& listItems = select->listItems();
1398 int size = listItems.size();
1400 bool multiple = select->multiple();
1401 ScopeArray<WebString> labels;
1402 labels.reset(new WebString[size]);
1404 // Check if popup already exists, close it if does.
1405 m_webPage->m_page->chrome()->client()->closePagePopup(0);
1409 bool* selecteds = 0;
1412 enableds = new bool[size];
1413 itemTypes = new int[size];
1414 selecteds = new bool[size];
1415 for (int i = 0; i < size; i++) {
1416 if (listItems[i]->hasTagName(HTMLNames::optionTag)) {
1417 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems[i]);
1418 labels[i] = option->textIndentedToRespectGroupLabel();
1419 enableds[i] = option->disabled() ? 0 : 1;
1420 selecteds[i] = option->selected();
1421 itemTypes[i] = option->parentNode() && option->parentNode()->hasTagName(HTMLNames::optgroupTag) ? TypeOptionInGroup : TypeOption;
1422 } else if (listItems[i]->hasTagName(HTMLNames::optgroupTag)) {
1423 HTMLOptGroupElement* optGroup = static_cast<HTMLOptGroupElement*>(listItems[i]);
1424 labels[i] = optGroup->groupLabelText();
1425 enableds[i] = optGroup->disabled() ? 0 : 1;
1426 selecteds[i] = false;
1427 itemTypes[i] = TypeGroup;
1428 } else if (listItems[i]->hasTagName(HTMLNames::hrTag)) {
1429 enableds[i] = false;
1430 selecteds[i] = false;
1431 itemTypes[i] = TypeSeparator;
1436 SelectPopupClient* selectClient = new SelectPopupClient(multiple, size, labels, enableds, itemTypes, selecteds, m_webPage, select);
1437 WebCore::IntRect elementRectInRootView = select->document()->view()->contentsToRootView(enclosingIntRect(select->getRect()));
1438 // Fail to create HTML popup, use the old path
1439 if (!m_webPage->m_page->chrome()->client()->openPagePopup(selectClient, elementRectInRootView))
1440 m_webPage->m_client->openPopupList(multiple, size, labels, enableds, itemTypes, selecteds);
1444 void InputHandler::setPopupListIndex(int index)
1446 if (index == -2) // Abandon
1447 return clearCurrentFocusElement();
1449 if (!isActiveSelectPopup())
1450 return clearCurrentFocusElement();
1452 RenderObject* renderer = m_currentFocusElement->renderer();
1453 if (renderer && renderer->isMenuList()) {
1454 RenderMenuList* renderMenu = toRenderMenuList(renderer);
1455 renderMenu->hidePopup();
1458 HTMLSelectElement* selectElement = static_cast<HTMLSelectElement*>(m_currentFocusElement.get());
1459 int optionIndex = selectElement->listToOptionIndex(index);
1460 selectElement->optionSelectedByUser(optionIndex, true /* deselect = true */, true /* fireOnChangeNow = false */);
1461 clearCurrentFocusElement();
1464 void InputHandler::setPopupListIndexes(int size, const bool* selecteds)
1466 if (!isActiveSelectPopup())
1467 return clearCurrentFocusElement();
1472 HTMLSelectElement* selectElement = static_cast<HTMLSelectElement*>(m_currentFocusElement.get());
1473 const WTF::Vector<HTMLElement*>& items = selectElement->listItems();
1474 if (items.size() != static_cast<unsigned int>(size))
1477 HTMLOptionElement* option;
1478 for (int i = 0; i < size; i++) {
1479 if (items[i]->hasTagName(HTMLNames::optionTag)) {
1480 option = static_cast<HTMLOptionElement*>(items[i]);
1481 option->setSelectedState(selecteds[i]);
1485 // Force repaint because we do not send mouse events to the select element
1486 // and the element doesn't automatically repaint itself.
1487 selectElement->dispatchFormControlChangeEvent();
1488 selectElement->renderer()->repaint();
1489 clearCurrentFocusElement();
1492 bool InputHandler::setBatchEditingActive(bool active)
1494 if (!isActiveTextEdit())
1497 ASSERT(m_currentFocusElement->document());
1498 ASSERT(m_currentFocusElement->document()->frame());
1500 // FIXME switch this to m_currentFocusElement->document()->frame() when we have separate
1501 // backingstore for each frame.
1502 BackingStoreClient* backingStoreClientForFrame = m_webPage->backingStoreClientForFrame(m_webPage->mainFrame());
1503 ASSERT(backingStoreClientForFrame);
1505 // Enable / Disable the backingstore to prevent visual updates.
1507 backingStoreClientForFrame->backingStore()->resumeScreenAndBackingStoreUpdates(BackingStore::RenderAndBlit);
1509 backingStoreClientForFrame->backingStore()->suspendScreenAndBackingStoreUpdates();
1514 int InputHandler::caretPosition() const
1516 if (!isActiveTextEdit())
1519 // NOTE: This function is expected to return the start of the selection if
1520 // a selection is applied.
1521 return selectionStart();
1524 int relativeLeftOffset(int caretPosition, int leftOffset)
1526 ASSERT(caretPosition >= 0);
1528 return std::max(0, caretPosition - leftOffset);
1531 int relativeRightOffset(int caretPosition, unsigned totalLengthOfText, int rightOffset)
1533 ASSERT(caretPosition >= 0);
1534 ASSERT(totalLengthOfText < INT_MAX);
1536 return std::min(caretPosition + rightOffset, static_cast<int>(totalLengthOfText));
1539 bool InputHandler::deleteTextRelativeToCursor(int leftOffset, int rightOffset)
1541 if (!isActiveTextEdit() || compositionActive())
1544 ProcessingChangeGuard guard(this);
1546 InputLog(LogLevelInfo, "InputHandler::deleteTextRelativeToCursor left %d right %d", leftOffset, rightOffset);
1548 int caretOffset = caretPosition();
1549 int start = relativeLeftOffset(caretOffset, leftOffset);
1550 int end = relativeRightOffset(caretOffset, elementText().length(), rightOffset);
1551 if (!deleteText(start, end))
1554 // Scroll the field if necessary. The automatic update is suppressed
1555 // by the processing change guard.
1556 ensureFocusTextElementVisible(EdgeIfNeeded);
1561 bool InputHandler::deleteText(int start, int end)
1563 if (!isActiveTextEdit())
1566 ProcessingChangeGuard guard(this);
1568 if (!setSelection(start, end, true /*changeIsPartOfComposition*/))
1571 InputLog(LogLevelInfo, "InputHandler::deleteText start %d end %d", start, end);
1573 return deleteSelection();
1576 spannable_string_t* InputHandler::spannableTextInRange(int start, int end, int32_t flags)
1578 if (!isActiveTextEdit())
1584 ASSERT(end > start);
1585 int length = end - start;
1587 WTF::String textString = elementText().substring(start, length);
1589 spannable_string_t* pst = (spannable_string_t*)fastMalloc(sizeof(spannable_string_t));
1591 // Don't use fastMalloc in case the string is unreasonably long. fastMalloc will
1592 // crash immediately on failure.
1593 pst->str = (wchar_t*)malloc(sizeof(wchar_t) * (length + 1));
1595 logAlways(LogLevelCritical, "InputHandler::spannableTextInRange Cannot allocate memory for string.\n");
1600 int stringLength = 0;
1601 if (!convertStringToWchar(textString, pst->str, length + 1, &stringLength)) {
1602 logAlways(LogLevelCritical, "InputHandler::spannableTextInRange failed to convert string.\n");
1608 pst->length = stringLength;
1609 pst->spans_count = 0;
1615 spannable_string_t* InputHandler::selectedText(int32_t flags)
1617 if (!isActiveTextEdit())
1620 return spannableTextInRange(selectionStart(), selectionEnd(), flags);
1623 spannable_string_t* InputHandler::textBeforeCursor(int32_t length, int32_t flags)
1625 if (!isActiveTextEdit())
1628 int caretOffset = caretPosition();
1629 int start = relativeLeftOffset(caretOffset, length);
1630 int end = caretOffset;
1632 return spannableTextInRange(start, end, flags);
1635 spannable_string_t* InputHandler::textAfterCursor(int32_t length, int32_t flags)
1637 if (!isActiveTextEdit())
1640 int caretOffset = caretPosition();
1641 int start = caretOffset;
1642 int end = relativeRightOffset(caretOffset, elementText().length(), length);
1644 return spannableTextInRange(start, end, flags);
1647 extracted_text_t* InputHandler::extractedTextRequest(extracted_text_request_t* request, int32_t flags)
1649 if (!isActiveTextEdit())
1652 extracted_text_t* extractedText = (extracted_text_t *)fastMalloc(sizeof(extracted_text_t));
1654 // 'flags' indicates whether the text is being monitored. This is not currently used.
1656 // FIXME add smart limiting based on the hint sizes. Currently return all text.
1658 extractedText->text = spannableTextInRange(0, elementText().length(), flags);
1660 // FIXME confirm these should be 0 on this requested. Text changes will likely require
1661 // the end be the length.
1662 extractedText->partial_start_offset = 0;
1663 extractedText->partial_end_offset = 0;
1664 extractedText->start_offset = 0;
1666 // Adjust selection values relative to the start offset, which may be a subset
1667 // of the text in the field.
1668 extractedText->selection_start = selectionStart() - extractedText->start_offset;
1669 extractedText->selection_end = selectionEnd() - extractedText->start_offset;
1671 // selectionActive is not limited to inside the extracted text.
1672 bool selectionActive = extractedText->selection_start != extractedText->selection_end;
1673 bool singleLine = m_currentFocusElement->hasTagName(HTMLNames::inputTag);
1675 // FIXME flags has two values in doc, enum not in header yet.
1676 extractedText->flags = selectionActive & singleLine;
1678 return extractedText;
1681 static void addCompositionTextStyleToAttributeTextStyle(AttributeTextStyle& style)
1683 style.setUnderline(AttributeTextStyle::StandardUnderline);
1686 static void addActiveTextStyleToAttributeTextStyle(AttributeTextStyle& style)
1688 style.setBackgroundColor(Color("blue"));
1689 style.setTextColor(Color("white"));
1692 static AttributeTextStyle compositionTextStyle()
1694 AttributeTextStyle style;
1695 addCompositionTextStyleToAttributeTextStyle(style);
1699 static AttributeTextStyle textStyleFromMask(int64_t mask)
1701 AttributeTextStyle style;
1702 if (mask & COMPOSED_TEXT_ATTRIB)
1703 addCompositionTextStyleToAttributeTextStyle(style);
1705 if (mask & ACTIVE_REGION_ATTRIB)
1706 addActiveTextStyleToAttributeTextStyle(style);
1711 bool InputHandler::removeComposedText()
1713 if (compositionActive()) {
1714 if (!deleteText(m_composingTextStart, m_composingTextEnd)) {
1715 // Could not remove the existing composition region.
1718 removeAttributedTextMarker();
1724 int32_t InputHandler::setComposingRegion(int32_t start, int32_t end)
1726 if (!isActiveTextEdit())
1729 if (!removeComposedText()) {
1730 // Could not remove the existing composition region.
1734 m_composingTextStart = start;
1735 m_composingTextEnd = end;
1737 if (compositionActive())
1738 addAttributedTextMarker(start, end, compositionTextStyle());
1740 InputLog(LogLevelInfo, "InputHandler::setComposingRegion start %d end %d", start, end);
1745 int32_t InputHandler::finishComposition()
1747 if (!isActiveTextEdit())
1750 // FIXME if no composition is active, should we return failure -1?
1751 if (!compositionActive())
1754 // Remove all markers.
1755 removeAttributedTextMarker();
1757 InputLog(LogLevelInfo, "InputHandler::finishComposition completed");
1762 span_t* InputHandler::firstSpanInString(spannable_string_t* spannableString, SpannableStringAttribute attrib)
1764 span_t* span = spannableString->spans;
1765 for (unsigned int i = 0; i < spannableString->spans_count; i++) {
1766 if (span->attributes_mask & attrib)
1774 bool InputHandler::isTrailingSingleCharacter(span_t* span, unsigned stringLength, unsigned composingTextLength)
1776 // Make sure the new string is one character larger than the old.
1777 if (composingTextLength != stringLength - 1)
1783 // Has only 1 character changed?
1784 if (span->start == span->end) {
1785 // Is this character the last character in the string?
1786 if (span->start == stringLength - 1)
1789 // Return after the first changed_attrib is found.
1793 bool InputHandler::setText(spannable_string_t* spannableString)
1795 if (!isActiveTextEdit() || !spannableString)
1798 ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1799 Frame* frame = m_currentFocusElement->document()->frame();
1801 Editor* editor = frame->editor();
1804 // Disable selectionHandler's active selection as we will be resetting and these
1805 // changes should not be handled as notification event.
1806 m_webPage->m_selectionHandler->setSelectionActive(false);
1808 String textToInsert = convertSpannableStringToString(spannableString);
1809 int textLength = textToInsert.length();
1811 InputLog(LogLevelInfo, "InputHandler::setText spannableString is '%s', of length %d \n", textToInsert.latin1().data(), textLength);
1813 span_t* changedSpan = firstSpanInString(spannableString, CHANGED_ATTRIB);
1814 int composingTextStart = m_composingTextStart;
1815 int composingTextEnd = m_composingTextEnd;
1816 int composingTextLength = compositionLength();
1817 removeAttributedTextMarker();
1819 if (isTrailingSingleCharacter(changedSpan, textLength, composingTextLength)) {
1820 // Handle the case where text is being composed.
1821 if (firstSpanInString(spannableString, COMPOSED_TEXT_ATTRIB)) {
1822 InputLog(LogLevelInfo, "InputHandler::setText Single trailing character detected. Text is being composed. \n");
1823 return editor->command("InsertText").execute(textToInsert.right(1));
1825 InputLog(LogLevelInfo, "InputHandler::setText Single trailing character detected. Text is not being composed. \n");
1826 return handleKeyboardInput(Platform::KeyboardEvent(textToInsert[textLength - 1], Platform::KeyboardEvent::KeyChar, 0), false /* changeIsPartOfComposition */);
1829 // If no spans have changed, treat it as a delete operation.
1831 // If the composition length is the same as our string length, then we don't need to do anything.
1832 if (composingTextLength == textLength) {
1833 InputLog(LogLevelInfo, "InputHandler::setText No spans have changed. New text is the same length as the old. Nothing to do. \n");
1837 if (composingTextLength - textLength == 1) {
1838 InputLog(LogLevelInfo, "InputHandler::setText No spans have changed. New text is one character shorter than the old. Treating as 'delete'. \n");
1839 return editor->command("DeleteBackward").execute();
1843 if (composingTextLength && !setSelection(composingTextStart, composingTextEnd, true /* changeIsPartOfComposition */))
1846 // If there is no text to add just delete.
1848 if (selectionActive())
1849 return editor->command("DeleteBackward").execute();
1855 // Triggering an insert of the text with a space character trailing
1856 // causes new text to adopt the previous text style.
1857 // Remove it and apply it as a keypress later.
1858 // Upstream Webkit bug created https://bugs.webkit.org/show_bug.cgi?id=70823
1859 bool requiresSpaceKeyPress = false;
1860 if (textLength > 0 && textToInsert[textLength - 1] == 32 /* space */) {
1861 requiresSpaceKeyPress = true;
1863 textToInsert.remove(textLength, 1);
1866 InputLog(LogLevelInfo, "InputHandler::setText Request being processed. Text before processing: '%s'", elementText().latin1().data());
1868 if (textLength == 1 && !spannableString->spans_count) {
1869 // Handle single key non-attributed entry as key press rather than insert to allow
1870 // triggering of javascript events.
1871 InputLog(LogLevelInfo, "InputHandler::setText Single character entry treated as key-press in the absense of spans. \n");
1872 return handleKeyboardInput(Platform::KeyboardEvent(textToInsert[0], Platform::KeyboardEvent::KeyChar, 0), true /* changeIsPartOfComposition */);
1875 // Perform the text change as a single command if there is one.
1876 if (!textToInsert.isEmpty() && !editor->command("InsertText").execute(textToInsert)) {
1877 InputLog(LogLevelWarn, "InputHandler::setText Failed to insert text '%s'", textToInsert.latin1().data());
1881 if (requiresSpaceKeyPress)
1882 handleKeyboardInput(Platform::KeyboardEvent(32 /* space */, Platform::KeyboardEvent::KeyChar, 0), true /* changeIsPartOfComposition */);
1884 InputLog(LogLevelInfo, "InputHandler::setText Request being processed. Text after processing '%s'", elementText().latin1().data());
1889 bool InputHandler::setTextAttributes(int insertionPoint, spannable_string_t* spannableString)
1891 // Apply the attributes to the field.
1892 span_t* span = spannableString->spans;
1893 for (unsigned int i = 0; i < spannableString->spans_count; i++) {
1894 unsigned int startPosition = insertionPoint + span->start;
1895 // The end point includes the character that it is before. Ie, 0, 0
1896 // applies to the first character as the end point includes the character
1897 // at the position. This means the endPosition is always +1.
1898 unsigned int endPosition = insertionPoint + span->end + 1;
1899 if (endPosition < startPosition || endPosition > elementText().length())
1902 if (!span->attributes_mask)
1903 continue; // Nothing to do.
1905 // MISSPELLED_WORD_ATTRIB is present as an option, but it is not currently
1906 // used by IMF. When they add support for on the fly spell checking we can
1907 // use it to apply spelling markers and disable continuous spell checking.
1909 InputLog(LogLevelInfo, "InputHandler::setTextAttributes adding marker %d to %d - %llu", startPosition, endPosition, span->attributes_mask);
1910 addAttributedTextMarker(startPosition, endPosition, textStyleFromMask(span->attributes_mask));
1915 InputLog(LogLevelInfo, "InputHandler::setTextAttributes attribute count %d", spannableString->spans_count);
1920 bool InputHandler::setRelativeCursorPosition(int insertionPoint, int relativeCursorPosition)
1922 if (!isActiveTextEdit())
1925 // 1 place cursor at end of insertion text.
1926 if (relativeCursorPosition == 1) {
1927 m_currentFocusElement->document()->frame()->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
1931 int cursorPosition = 0;
1932 if (relativeCursorPosition <= 0) {
1933 // Zero = insertionPoint
1934 // Negative value, move the cursor the requested number of characters before
1935 // the start of the inserted text.
1936 cursorPosition = insertionPoint + relativeCursorPosition;
1938 // Positive value, move the cursor the requested number of characters after
1939 // the end of the inserted text minus 1.
1940 cursorPosition = caretPosition() + relativeCursorPosition - 1;
1943 if (cursorPosition < 0 || cursorPosition > (int)elementText().length())
1946 InputLog(LogLevelInfo, "InputHandler::setRelativeCursorPosition cursor position %d", cursorPosition);
1948 return setCursorPosition(cursorPosition);
1951 bool InputHandler::setSpannableTextAndRelativeCursor(spannable_string_t* spannableString, int relativeCursorPosition, bool markTextAsComposing)
1953 InputLog(LogLevelInfo, "InputHandler::setSpannableTextAndRelativeCursor(%d, %d, %d)\n", spannableString->length, relativeCursorPosition, markTextAsComposing);
1954 int insertionPoint = compositionActive() ? m_composingTextStart : selectionStart();
1956 ProcessingChangeGuard guard(this);
1958 if (!setText(spannableString))
1961 if (!setTextAttributes(insertionPoint, spannableString))
1964 if (!setRelativeCursorPosition(insertionPoint, relativeCursorPosition))
1967 if (markTextAsComposing) {
1968 m_composingTextStart = insertionPoint;
1969 m_composingTextEnd = insertionPoint + spannableString->length;
1972 // Scroll the field if necessary. The automatic update is suppressed
1973 // by the processing change guard.
1974 ensureFocusTextElementVisible(EdgeIfNeeded);
1979 int32_t InputHandler::setComposingText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
1981 if (!isActiveTextEdit())
1984 if (!spannableString)
1987 InputLog(LogLevelInfo, "InputHandler::setComposingText at relativeCursorPosition: %d", relativeCursorPosition);
1989 // Enable input mode if we are processing a key event.
1990 setInputModeEnabled();
1992 return setSpannableTextAndRelativeCursor(spannableString, relativeCursorPosition, true /* markTextAsComposing */) ? 0 : -1;
1995 int32_t InputHandler::commitText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
1997 if (!isActiveTextEdit())
2000 if (!spannableString)
2003 InputLog(LogLevelInfo, "InputHandler::commitText");
2005 return setSpannableTextAndRelativeCursor(spannableString, relativeCursorPosition, false /* markTextAsComposing */) ? 0 : -1;