1/*
2 * Copyright (C) 2009, 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
3 *
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.
8 *
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.
13 *
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
17 */
18
19#include "config.h"
20#include "InputHandler.h"
21
22#include "BackingStore.h"
23#include "BackingStoreClient.h"
24#include "CSSStyleDeclaration.h"
25#include "Chrome.h"
26#include "ColorPickerClient.h"
27#include "DOMSupport.h"
28#include "DatePickerClient.h"
29#include "Document.h"
30#include "DocumentLoader.h"
31#include "DocumentMarkerController.h"
32#include "EditorClientBlackBerry.h"
33#include "FocusController.h"
34#include "Frame.h"
35#include "FrameView.h"
36#include "HTMLFormElement.h"
37#include "HTMLInputElement.h"
38#include "HTMLNames.h"
39#include "HTMLOptGroupElement.h"
40#include "HTMLOptionElement.h"
41#include "HTMLSelectElement.h"
42#include "HTMLTextAreaElement.h"
43#include "NotImplemented.h"
44#include "Page.h"
45#include "PlatformKeyboardEvent.h"
46#include "PluginView.h"
47#include "Range.h"
48#include "RenderLayer.h"
49#include "RenderMenuList.h"
50#include "RenderPart.h"
51#include "RenderText.h"
52#include "RenderTextControl.h"
53#include "RenderWidget.h"
54#include "RenderedDocumentMarker.h"
55#include "ScopePointer.h"
56#include "SelectPopupClient.h"
57#include "SelectionHandler.h"
58#include "SpellChecker.h"
59#include "SpellingHandler.h"
60#include "SuggestionBoxHandler.h"
61#include "TextCheckerClient.h"
62#include "TextIterator.h"
63#include "VisiblePosition.h"
64#include "VisibleUnits.h"
65#include "WebKitThreadViewportAccessor.h"
66#include "WebPageClient.h"
67#include "WebPage_p.h"
68#include "WebSettings.h"
69#include "htmlediting.h"
70
71#include <BlackBerryPlatformDeviceInfo.h>
72#include <BlackBerryPlatformIMF.h>
73#include <BlackBerryPlatformKeyboardEvent.h>
74#include <BlackBerryPlatformLog.h>
75#include <BlackBerryPlatformScreen.h>
76#include <BlackBerryPlatformSettings.h>
77#include <cmath>
78#include <sys/keycodes.h>
79#include <wtf/text/CString.h>
80
81#define ENABLE_INPUT_LOG 0
82#define ENABLE_FOCUS_LOG 0
83#define ENABLE_SPELLING_LOG 0
84
85using namespace BlackBerry::Platform;
86using namespace WebCore;
87
88#if ENABLE_INPUT_LOG
89#define InputLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
90#else
91#define InputLog(severity, format, ...)
92#endif // ENABLE_INPUT_LOG
93
94#if ENABLE_FOCUS_LOG
95#define FocusLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
96#else
97#define FocusLog(severity, format, ...)
98#endif // ENABLE_FOCUS_LOG
99
100#if ENABLE_SPELLING_LOG
101#define SpellingLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
102#else
103#define SpellingLog(severity, format, ...)
104#endif // ENABLE_SPELLING_LOG
105
106namespace BlackBerry {
107namespace WebKit {
108
109static const float zoomAnimationThreshold = 0.5;
110
111class ProcessingChangeGuard {
112public:
113    ProcessingChangeGuard(InputHandler* inputHandler)
114        : m_inputHandler(inputHandler)
115        , m_savedProcessingChange(false)
116    {
117        ASSERT(m_inputHandler);
118
119        m_savedProcessingChange = m_inputHandler->processingChange();
120        m_inputHandler->setProcessingChange(true);
121    }
122
123    ~ProcessingChangeGuard()
124    {
125        m_inputHandler->setProcessingChange(m_savedProcessingChange);
126    }
127
128private:
129    InputHandler* m_inputHandler;
130    bool m_savedProcessingChange;
131};
132
133InputHandler::InputHandler(WebPagePrivate* page)
134    : m_webPage(page)
135    , m_currentFocusElement(0)
136    , m_previousFocusableTextElement(0)
137    , m_nextFocusableTextElement(0)
138    , m_hasSubmitButton(false)
139    , m_inputModeEnabled(false)
140    , m_processingChange(false)
141    , m_shouldEnsureFocusTextElementVisibleOnSelectionChanged(false)
142    , m_currentFocusElementType(TextEdit)
143    , m_currentFocusElementTextEditMask(DEFAULT_STYLE)
144    , m_composingTextStart(0)
145    , m_composingTextEnd(0)
146    , m_pendingKeyboardVisibilityChange(NoChange)
147    , m_delayKeyboardVisibilityChange(false)
148    , m_sendFormStateOnNextKeyboardRequest(false)
149    , m_request(0)
150    , m_processingTransactionId(-1)
151    , m_shouldNotifyWebView(true)
152    , m_expectedKeyUpChar(0)
153    , m_didSpellCheckWord(false)
154    , m_spellingHandler(new SpellingHandler(this))
155    , m_spellCheckStatusConfirmed(false)
156    , m_globalSpellCheckStatus(false)
157    , m_minimumSpellCheckingRequestSequence(-1)
158    , m_elementTouchedIsCrossFrame(false)
159{
160}
161
162InputHandler::~InputHandler()
163{
164    delete m_spellingHandler;
165}
166
167static BlackBerryInputType convertInputType(const HTMLInputElement* inputElement)
168{
169    if (inputElement->isPasswordField())
170        return InputTypePassword;
171    if (inputElement->isSearchField())
172        return InputTypeSearch;
173    if (inputElement->isEmailField())
174        return InputTypeEmail;
175    if (inputElement->isMonthField())
176        return InputTypeMonth;
177    if (inputElement->isNumberField())
178        return InputTypeNumber;
179    if (inputElement->isTelephoneField())
180        return InputTypeTelephone;
181    if (inputElement->isURLField())
182        return InputTypeURL;
183#if ENABLE(INPUT_TYPE_COLOR)
184    if (inputElement->isColorControl())
185        return InputTypeColor;
186#endif
187    if (inputElement->isDateField())
188        return InputTypeDate;
189    if (inputElement->isDateTimeField())
190        return InputTypeDateTime;
191    if (inputElement->isDateTimeLocalField())
192        return InputTypeDateTimeLocal;
193    if (inputElement->isTimeField())
194        return InputTypeTime;
195    // FIXME: missing WEEK popup selector
196    if (DOMSupport::elementIdOrNameIndicatesEmail(inputElement))
197        return InputTypeEmail;
198    if (DOMSupport::elementIdOrNameIndicatesUrl(inputElement))
199        return InputTypeURL;
200    if (DOMSupport::elementPatternIndicatesNumber(inputElement))
201        return InputTypeNumber;
202    if (DOMSupport::elementPatternIndicatesHexadecimal(inputElement))
203        return InputTypeHexadecimal;
204
205    return InputTypeText;
206}
207
208static int64_t inputStyle(BlackBerryInputType type, const Element* element)
209{
210    switch (type) {
211    case InputTypeEmail:
212    case InputTypeURL:
213    case InputTypeSearch:
214    case InputTypeText:
215    case InputTypeTextArea:
216        {
217            DOMSupport::AttributeState autoCompleteState = DOMSupport::elementSupportsAutocomplete(element);
218            DOMSupport::AttributeState autoCorrectState = DOMSupport::elementSupportsAutocorrect(element);
219
220            // Autocomplete disabled explicitly.
221            if (autoCompleteState == DOMSupport::Off) {
222                if (autoCorrectState == DOMSupport::On)
223                    return NO_PREDICTION;
224                return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
225            }
226
227            // Autocomplete enabled explicitly.
228            if (autoCompleteState == DOMSupport::On) {
229                if (autoCorrectState == DOMSupport::Off)
230                    return NO_AUTO_TEXT | NO_AUTO_CORRECTION;
231                return DEFAULT_STYLE;
232            }
233
234            // Check for reserved strings and known types.
235            if (type == InputTypeEmail || type == InputTypeURL || DOMSupport::elementIdOrNameIndicatesNoAutocomplete(element)) {
236                if (autoCorrectState == DOMSupport::On)
237                    return NO_PREDICTION;
238                return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
239            }
240
241            // Autocomplete state wasn't provided if it is a text area or autocorrect is on, apply support.
242            if (autoCorrectState == DOMSupport::On || (type == InputTypeTextArea && autoCorrectState != DOMSupport::Off))
243                return DEFAULT_STYLE;
244
245            // Single line text input, without special features explicitly turned on or a textarea
246            // with autocorrect disabled explicitly. Only enable predictions.
247            return NO_AUTO_TEXT | NO_AUTO_CORRECTION;
248        }
249    case InputTypePassword:
250    case InputTypeNumber:
251    case InputTypeTelephone:
252    case InputTypeHexadecimal:
253        // Disable special handling.
254        return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
255    default:
256        break;
257    }
258    return DEFAULT_STYLE;
259}
260
261static VirtualKeyboardType convertInputTypeToVKBType(BlackBerryInputType inputType)
262{
263    switch (inputType) {
264    case InputTypeURL:
265        return VKBTypeUrl;
266    case InputTypeEmail:
267        return VKBTypeEmail;
268    case InputTypeTelephone:
269        return VKBTypePhone;
270    case InputTypePassword:
271        return VKBTypePassword;
272    case InputTypeNumber:
273    case InputTypeHexadecimal:
274        return VKBTypePin;
275    default:
276        // All other types are text based use default keyboard.
277        return VKBTypeDefault;
278    }
279}
280
281static VirtualKeyboardType convertStringToKeyboardType(const AtomicString& string)
282{
283    DEFINE_STATIC_LOCAL(AtomicString, Default, ("default"));
284    DEFINE_STATIC_LOCAL(AtomicString, Url, ("url"));
285    DEFINE_STATIC_LOCAL(AtomicString, Email, ("email"));
286    DEFINE_STATIC_LOCAL(AtomicString, Password, ("password"));
287    DEFINE_STATIC_LOCAL(AtomicString, Web, ("web"));
288    DEFINE_STATIC_LOCAL(AtomicString, Number, ("number"));
289    DEFINE_STATIC_LOCAL(AtomicString, Symbol, ("symbol"));
290    DEFINE_STATIC_LOCAL(AtomicString, Phone, ("phone"));
291    DEFINE_STATIC_LOCAL(AtomicString, Pin, ("pin"));
292    DEFINE_STATIC_LOCAL(AtomicString, Hex, ("hexadecimal"));
293
294    if (string.isEmpty())
295        return VKBTypeNotSet;
296    if (equalIgnoringCase(string, Default))
297        return VKBTypeDefault;
298    if (equalIgnoringCase(string, Url))
299        return VKBTypeUrl;
300    if (equalIgnoringCase(string, Email))
301        return VKBTypeEmail;
302    if (equalIgnoringCase(string, Password))
303        return VKBTypePassword;
304    if (equalIgnoringCase(string, Web))
305        return VKBTypeWeb;
306    if (equalIgnoringCase(string, Number))
307        return VKBTypeNumPunc;
308    if (equalIgnoringCase(string, Symbol))
309        return VKBTypeSymbol;
310    if (equalIgnoringCase(string, Phone))
311        return VKBTypePhone;
312    if (equalIgnoringCase(string, Pin) || equalIgnoringCase(string, Hex))
313        return VKBTypePin;
314    return VKBTypeNotSet;
315}
316
317static VirtualKeyboardType keyboardTypeAttribute(const WebCore::Element* element)
318{
319    DEFINE_STATIC_LOCAL(QualifiedName, keyboardTypeAttr, (nullAtom, "data-blackberry-virtual-keyboard-type", nullAtom));
320
321    if (element->fastHasAttribute(keyboardTypeAttr)) {
322        AtomicString attributeString = element->fastGetAttribute(keyboardTypeAttr);
323        return convertStringToKeyboardType(attributeString);
324    }
325
326    if (element->isFormControlElement()) {
327        const HTMLFormControlElement* formElement = static_cast<const HTMLFormControlElement*>(element);
328        if (formElement->form() && formElement->form()->fastHasAttribute(keyboardTypeAttr)) {
329            AtomicString attributeString = formElement->form()->fastGetAttribute(keyboardTypeAttr);
330            return convertStringToKeyboardType(attributeString);
331        }
332    }
333
334    return VKBTypeNotSet;
335}
336
337static VirtualKeyboardEnterKeyType convertStringToKeyboardEnterKeyType(const AtomicString& string)
338{
339    DEFINE_STATIC_LOCAL(AtomicString, Default, ("default"));
340    DEFINE_STATIC_LOCAL(AtomicString, Connect, ("connect"));
341    DEFINE_STATIC_LOCAL(AtomicString, Done, ("done"));
342    DEFINE_STATIC_LOCAL(AtomicString, Go, ("go"));
343    DEFINE_STATIC_LOCAL(AtomicString, Join, ("join"));
344    DEFINE_STATIC_LOCAL(AtomicString, Next, ("next"));
345    DEFINE_STATIC_LOCAL(AtomicString, Search, ("search"));
346    DEFINE_STATIC_LOCAL(AtomicString, Send, ("send"));
347    DEFINE_STATIC_LOCAL(AtomicString, Submit, ("submit"));
348
349    if (string.isEmpty())
350        return VKBEnterKeyNotSet;
351    if (equalIgnoringCase(string, Default))
352        return VKBEnterKeyDefault;
353    if (equalIgnoringCase(string, Connect))
354        return VKBEnterKeyConnect;
355    if (equalIgnoringCase(string, Done))
356        return VKBEnterKeyDone;
357    if (equalIgnoringCase(string, Go))
358        return VKBEnterKeyGo;
359    if (equalIgnoringCase(string, Join))
360        return VKBEnterKeyJoin;
361    if (equalIgnoringCase(string, Next))
362        return VKBEnterKeyNext;
363    if (equalIgnoringCase(string, Search))
364        return VKBEnterKeySearch;
365    if (equalIgnoringCase(string, Send))
366        return VKBEnterKeySend;
367    if (equalIgnoringCase(string, Submit))
368        return VKBEnterKeySubmit;
369    return VKBEnterKeyNotSet;
370}
371
372static VirtualKeyboardEnterKeyType keyboardEnterKeyTypeAttribute(const WebCore::Element* element)
373{
374    DEFINE_STATIC_LOCAL(QualifiedName, keyboardEnterKeyTypeAttr, (nullAtom, "data-blackberry-virtual-keyboard-enter-key", nullAtom));
375
376    if (element->fastHasAttribute(keyboardEnterKeyTypeAttr)) {
377        AtomicString attributeString = element->fastGetAttribute(keyboardEnterKeyTypeAttr);
378        return convertStringToKeyboardEnterKeyType(attributeString);
379    }
380
381    if (element->isFormControlElement()) {
382        const HTMLFormControlElement* formElement = static_cast<const HTMLFormControlElement*>(element);
383        if (formElement->form() && formElement->form()->fastHasAttribute(keyboardEnterKeyTypeAttr)) {
384            AtomicString attributeString = formElement->form()->fastGetAttribute(keyboardEnterKeyTypeAttr);
385            return convertStringToKeyboardEnterKeyType(attributeString);
386        }
387    }
388
389    return VKBEnterKeyNotSet;
390}
391
392void InputHandler::setProcessingChange(bool processingChange)
393{
394    if (processingChange == m_processingChange)
395        return;
396
397    m_processingChange = processingChange;
398
399    if (!m_processingChange)
400        m_webPage->m_selectionHandler->inputHandlerDidFinishProcessingChange();
401}
402
403WTF::String InputHandler::elementText()
404{
405    if (!isActiveTextEdit())
406        return WTF::String();
407
408    return DOMSupport::inputElementText(m_currentFocusElement.get());
409}
410
411BlackBerryInputType InputHandler::elementType(Element* element) const
412{
413    if (const HTMLInputElement* inputElement = static_cast<const HTMLInputElement*>(element->toInputElement()))
414        return convertInputType(inputElement);
415
416    if (element->hasTagName(HTMLNames::textareaTag))
417        return InputTypeTextArea;
418
419    // Default to InputTypeTextArea for content editable fields.
420    return InputTypeTextArea;
421}
422
423void InputHandler::focusedNodeChanged()
424{
425    ASSERT(m_webPage->m_page->focusController());
426    Frame* frame = m_webPage->m_page->focusController()->focusedOrMainFrame();
427    if (!frame || !frame->document())
428        return;
429
430    Node* node = frame->document()->focusedElement();
431
432    if (isActiveTextEdit() && m_currentFocusElement == node) {
433        notifyClientOfKeyboardVisibilityChange(true);
434        return;
435    }
436
437    if (node && node->isElementNode()) {
438        Element* element = toElement(node);
439        if (DOMSupport::isElementTypePlugin(element)) {
440            setPluginFocused(element);
441            return;
442        }
443
444        if (DOMSupport::isTextBasedContentEditableElement(element) && !DOMSupport::isElementReadOnly(element)) {
445            // Focused node is a text based input field, textarea or content editable field.
446            setElementFocused(element);
447            return;
448        }
449    } else if (node && DOMSupport::isTextBasedContentEditableElement(node->parentElement()) && !DOMSupport::isElementReadOnly(node->parentElement())) {
450        setElementFocused(node->parentElement());
451        return;
452    }
453
454    if (isActiveTextEdit() && m_currentFocusElement->isContentEditable()) {
455        // This is a special handler for content editable fields. The focus node is the top most
456        // field that is content editable, however, by enabling / disabling designmode and
457        // content editable, it is possible through javascript or selection to trigger the focus node to
458        // change even while maintaining editing on the same selection point. If the focus element
459        // isn't contentEditable, but the current selection is, don't send a change notification.
460
461        // When processing changes selection changes occur that may reset the focus element
462        // and could potentially cause a crash as m_currentFocusElement should not be
463        // changed during processing of an EditorCommand.
464        if (processingChange())
465            return;
466
467        Frame* frame = m_currentFocusElement->document()->frame();
468        ASSERT(frame);
469
470        // Test the current selection to make sure that the content in focus is still content
471        // editable. This may mean Javascript triggered a focus change by modifying the
472        // top level parent of this object's content editable state without actually modifying
473        // this particular object.
474        // Example site: html5demos.com/contentEditable - blur event triggers focus change.
475        if (frame == m_webPage->focusedOrMainFrame()
476            && frame->selection()->start().anchorNode()
477            && frame->selection()->start().anchorNode()->isContentEditable()
478            && !m_elementTouchedIsCrossFrame)
479                return;
480    }
481
482    // No valid focus element found for handling.
483    setElementUnfocused();
484}
485
486void InputHandler::setPluginFocused(Element* element)
487{
488    ASSERT(DOMSupport::isElementTypePlugin(element));
489
490    if (isActiveTextEdit())
491        setElementUnfocused();
492
493    m_currentFocusElementType = Plugin;
494    m_currentFocusElement = element;
495}
496
497static bool convertStringToWchar(const WTF::String& string, wchar_t* dest, int destCapacity, int* destLength)
498{
499    ASSERT(dest);
500
501    int length = string.length();
502
503    if (!length) {
504        destLength = 0;
505        return true;
506    }
507
508    UErrorCode ec = U_ZERO_ERROR;
509
510    // wchar_t strings sent to IMF are 32 bit so casting to UChar32 is safe.
511    u_strToUTF32(reinterpret_cast<UChar32*>(dest), destCapacity, destLength, string.characters(), length, &ec);
512    if (ec) {
513        Platform::logAlways(Platform::LogLevelCritical, "InputHandler::convertStringToWchar Error converting string ec (%d).", ec);
514        destLength = 0;
515        return false;
516    }
517    return true;
518}
519
520static bool convertStringToWcharVector(const WTF::String& string, WTF::Vector<wchar_t>& wcharString)
521{
522    ASSERT(wcharString.isEmpty());
523
524    int length = string.length();
525    if (!length)
526        return true;
527
528    if (!wcharString.tryReserveCapacity(length + 1)) {
529        Platform::logAlways(Platform::LogLevelCritical, "InputHandler::convertStringToWcharVector Cannot allocate memory for string.");
530        return false;
531    }
532
533    int destLength = 0;
534    if (!convertStringToWchar(string, wcharString.data(), length + 1, &destLength))
535        return false;
536
537    wcharString.resize(destLength);
538    return true;
539}
540
541static WTF::String convertSpannableStringToString(spannable_string_t* src)
542{
543    if (!src || !src->str || !src->length)
544        return WTF::String();
545
546    WTF::Vector<UChar> dest;
547    int destCapacity = (src->length * 2) + 1;
548    if (!dest.tryReserveCapacity(destCapacity)) {
549        Platform::logAlways(Platform::LogLevelCritical, "InputHandler::convertSpannableStringToString Cannot allocate memory for string.");
550        return WTF::String();
551    }
552
553    int destLength = 0;
554    UErrorCode ec = U_ZERO_ERROR;
555    // wchar_t strings sent from IMF are 32 bit so casting to UChar32 is safe.
556    u_strFromUTF32(dest.data(), destCapacity, &destLength, reinterpret_cast<UChar32*>(src->str), src->length, &ec);
557    if (ec) {
558        Platform::logAlways(Platform::LogLevelCritical, "InputHandler::convertSpannableStringToString Error converting string ec (%d).", ec);
559        return WTF::String();
560    }
561    dest.resize(destLength);
562    return WTF::String(dest.data(), destLength);
563}
564
565void InputHandler::sendLearnTextDetails(const WTF::String& string)
566{
567    Vector<wchar_t> wcharString;
568    if (!convertStringToWcharVector(string, wcharString) || wcharString.isEmpty())
569        return;
570
571    m_webPage->m_client->inputLearnText(wcharString.data(), wcharString.size());
572}
573
574void InputHandler::learnText()
575{
576    if (!isActiveTextEdit())
577        return;
578
579    // Do not send (or calculate) the text when the field is NO_PREDICTION or NO_AUTO_TEXT.
580    if (m_currentFocusElementTextEditMask & NO_PREDICTION || m_currentFocusElementTextEditMask & NO_AUTO_TEXT)
581        return;
582
583    WTF::String textInField(elementText());
584    if (textInField.length() >= MaxLearnTextDataSize)
585        textInField = textInField.substring(std::max(0, static_cast<int>(caretPosition() - MaxLearnTextDataSize)), std::min(textInField.length(), MaxLearnTextDataSize));
586
587    textInField = textInField.stripWhiteSpace();
588
589    // Build up the 500 character strings in word chunks.
590    // Spec says 1000, but memory corruption has been observed.
591    ASSERT(textInField.length() <= MaxLearnTextDataSize);
592
593    if (textInField.isEmpty())
594        return;
595
596    InputLog(Platform::LogLevelInfo, "InputHandler::learnText '%s'", textInField.latin1().data());
597    sendLearnTextDetails(textInField);
598}
599
600void InputHandler::callRequestCheckingFor(PassRefPtr<WebCore::SpellCheckRequest> spellCheckRequest)
601{
602    if (SpellChecker* spellChecker = getSpellChecker())
603        spellChecker->requestCheckingFor(spellCheckRequest);
604}
605
606void InputHandler::requestCheckingOfString(PassRefPtr<WebCore::SpellCheckRequest> spellCheckRequest)
607{
608    SpellingLog(Platform::LogLevelInfo, "InputHandler::requestCheckingOfString '%s'", spellCheckRequest->data().text().latin1().data());
609
610    if (!spellCheckRequest) {
611        SpellingLog(Platform::LogLevelWarn, "InputHandler::requestCheckingOfString did not receive a valid request.");
612        return;
613    }
614
615    if (spellCheckRequest->data().sequence() <= m_minimumSpellCheckingRequestSequence) {
616        SpellingLog(Platform::LogLevelWarn, "InputHandler::requestCheckingOfString rejecting stale request with sequenceId=%d. Sentinal currently at %d."
617            , spellCheckRequest->data().sequence(), m_minimumSpellCheckingRequestSequence);
618        spellCheckRequest->didCancel();
619        return;
620    }
621
622    unsigned requestLength = spellCheckRequest->data().text().length();
623
624    // Check if the field should be spellchecked.
625    if (!isActiveTextEdit() || !shouldSpellCheckElement(m_currentFocusElement.get()) || requestLength < 2) {
626        SpellingLog(Platform::LogLevelWarn, "InputHandler::requestCheckingOfString request cancelled");
627        spellCheckRequest->didCancel();
628        return;
629    }
630
631    if (requestLength >= MaxSpellCheckingStringLength) {
632        // Cancel this request and send it off in newly created chunks.
633        spellCheckRequest->didCancel();
634        m_spellingHandler->spellCheckTextBlock(m_currentFocusElement.get(), TextCheckingProcessIncremental);
635        return;
636    }
637
638    wchar_t* checkingString = (wchar_t*)malloc(sizeof(wchar_t) * (requestLength + 1));
639    if (!checkingString) {
640        Platform::logAlways(Platform::LogLevelCritical, "InputHandler::requestCheckingOfString Cannot allocate memory for string.");
641        spellCheckRequest->didCancel();
642        return;
643    }
644
645    int paragraphLength = 0;
646    if (!convertStringToWchar(spellCheckRequest->data().text(), checkingString, requestLength + 1, &paragraphLength)) {
647        Platform::logAlways(Platform::LogLevelCritical, "InputHandler::requestCheckingOfString Failed to convert String to wchar type.");
648        free(checkingString);
649        spellCheckRequest->didCancel();
650        return;
651    }
652
653    m_processingTransactionId = m_webPage->m_client->checkSpellingOfStringAsync(checkingString, static_cast<unsigned>(paragraphLength));
654    free(checkingString);
655
656    // If the call to the input service did not go through, then cancel the request so we don't block endlessly.
657    // This should still take transactionId as a parameter to maintain the same behavior as if InputMethodSupport
658    // were to cancel a request during processing.
659    if (m_processingTransactionId == -1) { // Error before sending request to input service.
660        spellCheckRequest->didCancel();
661        return;
662    }
663
664    m_request = spellCheckRequest;
665}
666
667void InputHandler::spellCheckingRequestProcessed(int32_t transactionId, spannable_string_t* spannableString)
668{
669#if !ENABLE_SPELLING_LOG
670    UNUSED_PARAM(transactionId)
671#endif
672
673    SpellingLog(Platform::LogLevelWarn,
674        "InputHandler::spellCheckingRequestProcessed Expected transaction id %d, received %d. %s",
675        m_processingTransactionId,
676        transactionId,
677        transactionId == m_processingTransactionId ? "" : "We are out of sync with input service.");
678
679    if (!spannableString
680        || !isActiveTextEdit()
681        || !DOMSupport::elementHasContinuousSpellCheckingEnabled(m_currentFocusElement)
682        || !m_processingTransactionId
683        || !m_request) {
684        SpellingLog(Platform::LogLevelWarn, "InputHandler::spellCheckingRequestProcessed Cancelling request with transactionId %d.", transactionId);
685        if (m_request) {
686            m_request->didCancel();
687            m_request = 0;
688        }
689        m_processingTransactionId = -1;
690        return;
691    }
692
693    Vector<TextCheckingResult> results;
694
695    // Convert the spannableString to TextCheckingResult then append to results vector.
696    WTF::String replacement;
697    TextCheckingResult textCheckingResult;
698    textCheckingResult.type = TextCheckingTypeSpelling;
699    textCheckingResult.replacement = replacement;
700    textCheckingResult.location = 0;
701    textCheckingResult.length = 0;
702
703    span_t* span = spannableString->spans;
704    for (unsigned i = 0; i < spannableString->spans_count; i++) {
705        if (!span)
706            break;
707        if (span->end < span->start) {
708            m_request->didCancel();
709            m_request = 0;
710            return;
711        }
712        if (span->attributes_mask & MISSPELLED_WORD_ATTRIB) {
713            textCheckingResult.location = span->start;
714            // The end point includes the character that it is before. Ie, 0, 0
715            // applies to the first character as the end point includes the character
716            // at the position. This means the endPosition is always +1.
717            textCheckingResult.length = span->end - span->start + 1;
718            results.append(textCheckingResult);
719        }
720        span++;
721    }
722
723    // free data that we malloc'ed in InputMethodSupport
724    free(spannableString->spans);
725    free(spannableString);
726
727    m_request->didSucceed(results);
728    m_request = 0;
729}
730
731SpellChecker* InputHandler::getSpellChecker()
732{
733    if (!m_currentFocusElement || !m_currentFocusElement->document())
734        return 0;
735
736    if (Frame* frame = m_currentFocusElement->document()->frame())
737        if (Editor* editor = frame->editor())
738            return editor->spellChecker();
739
740    return 0;
741}
742
743bool InputHandler::shouldRequestSpellCheckingOptionsForPoint(const Platform::IntPoint& documentContentPosition, const Element* touchedElement, imf_sp_text_t& spellCheckingOptionRequest)
744{
745    if (!isActiveTextEdit())
746        return false;
747
748    Element* currentFocusElement = m_currentFocusElement.get();
749    if (!currentFocusElement || !currentFocusElement->isElementNode())
750        return false;
751
752    while (!currentFocusElement->isRootEditableElement()) {
753        Element* parentElement = currentFocusElement->parentElement();
754        if (!parentElement)
755            break;
756        currentFocusElement = parentElement;
757    }
758
759    if (touchedElement != currentFocusElement)
760        return false;
761
762    LayoutPoint contentPos(documentContentPosition);
763    contentPos = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), m_webPage->focusedOrMainFrame(), roundedIntPoint(contentPos));
764
765    Document* document = currentFocusElement->document();
766    ASSERT(document);
767
768    RenderedDocumentMarker* marker = document->markers()->renderedMarkerContainingPoint(contentPos, DocumentMarker::Spelling);
769    if (!marker)
770        return false;
771
772    m_didSpellCheckWord = true;
773
774    // Populate the marker details in preparation for the request as the marker is
775    // not guaranteed to be valid after the cursor is placed.
776    spellCheckingOptionRequest.startTextPosition = marker->startOffset();
777    spellCheckingOptionRequest.endTextPosition = marker->endOffset();
778
779    m_spellCheckingOptionsRequest.startTextPosition = 0;
780    m_spellCheckingOptionsRequest.endTextPosition = 0;
781
782    SpellingLog(Platform::LogLevelInfo,
783        "InputHandler::shouldRequestSpellCheckingOptionsForPoint Found spelling marker at point %s\nMarker start %d end %d",
784        documentContentPosition.toString().c_str(),
785        spellCheckingOptionRequest.startTextPosition,
786        spellCheckingOptionRequest.endTextPosition);
787
788    return true;
789}
790
791void InputHandler::requestSpellingCheckingOptions(imf_sp_text_t& spellCheckingOptionRequest, WebCore::IntSize& screenOffset, const bool shouldMoveDialog)
792{
793    // If the caret is no longer active, no message should be sent.
794    if (m_webPage->focusedOrMainFrame()->selection()->selectionType() != VisibleSelection::CaretSelection)
795        return;
796
797    if (!m_currentFocusElement || !m_currentFocusElement->document() || !m_currentFocusElement->document()->frame())
798        return;
799
800    if (shouldMoveDialog || !(spellCheckingOptionRequest.startTextPosition || spellCheckingOptionRequest.startTextPosition)) {
801        if (m_spellCheckingOptionsRequest.startTextPosition || m_spellCheckingOptionsRequest.endTextPosition)
802            spellCheckingOptionRequest = m_spellCheckingOptionsRequest;
803    }
804
805    if (!shouldMoveDialog && spellCheckingOptionRequest.startTextPosition == spellCheckingOptionRequest.endTextPosition)
806        return;
807
808    if (screenOffset.width() == -1 && screenOffset.height() == -1) {
809        screenOffset.setWidth(m_screenOffset.width());
810        screenOffset.setHeight(m_screenOffset.height());
811    } else {
812        m_screenOffset.setWidth(screenOffset.width());
813        m_screenOffset.setHeight(screenOffset.height());
814    }
815
816    // imf_sp_text_t should be generated in pixel viewport coordinates.
817    // Caret is in document coordinates.
818    WebCore::IntRect caretRect = m_webPage->focusedOrMainFrame()->selection()->selection().visibleStart().absoluteCaretBounds();
819
820    // Shift from posible iFrame to root view/main frame.
821    caretRect = m_webPage->focusedOrMainFrame()->view()->contentsToRootView(caretRect);
822
823    // Shift to document scroll position.
824    const WebCore::IntPoint scrollPosition = m_webPage->mainFrame()->view()->scrollPosition();
825    caretRect.move(scrollPosition.x(), scrollPosition.y());
826
827    // If we are only moving the dialog, we don't need to provide startTextPosition and endTextPosition so this logic can be skipped.
828    if (!shouldMoveDialog) {
829        // Calculate the offset for contentEditable since the marker offsets are relative to the node.
830        // Get caret position. Though the spelling markers might no longer exist, if this method is called we can assume the caret was placed on top of a marker earlier.
831        VisiblePosition caretPosition = m_currentFocusElement->document()->frame()->selection()->selection().visibleStart();
832
833        if (HTMLTextFormControlElement* controlElement = DOMSupport::toTextControlElement(m_currentFocusElement.get())) {
834            spellCheckingOptionRequest.startTextPosition = controlElement->indexForVisiblePosition(startOfWord(caretPosition));
835            spellCheckingOptionRequest.endTextPosition = controlElement->indexForVisiblePosition(endOfWord(caretPosition));
836        } else {
837            unsigned location = 0;
838            unsigned length = 0;
839
840            // Create a range from the start to end of word.
841            RefPtr<Range> rangeSelection = VisibleSelection(startOfWord(caretPosition), endOfWord(caretPosition)).toNormalizedRange();
842            if (!rangeSelection)
843                return;
844
845            TextIterator::getLocationAndLengthFromRange(m_currentFocusElement.get(), rangeSelection.get(), location, length);
846            spellCheckingOptionRequest.startTextPosition = location;
847            spellCheckingOptionRequest.endTextPosition = location + length;
848        }
849    }
850    m_spellCheckingOptionsRequest = spellCheckingOptionRequest;
851
852    InputLog(Platform::LogLevelInfo,
853        "InputHandler::requestSpellingCheckingOptions caretRect topLeft=%s, bottomRight=%s, startTextPosition=%d, endTextPosition=%d",
854        Platform::IntPoint(caretRect.minXMinYCorner()).toString().c_str(),
855        Platform::IntPoint(caretRect.maxXMaxYCorner()).toString().c_str(),
856        spellCheckingOptionRequest.startTextPosition,
857        spellCheckingOptionRequest.endTextPosition);
858
859    m_webPage->m_client->requestSpellingCheckingOptions(spellCheckingOptionRequest, caretRect, screenOffset, shouldMoveDialog);
860}
861
862void InputHandler::setElementUnfocused(bool refocusOccuring)
863{
864    if (isActiveTextEdit() && DOMSupport::isElementAndDocumentAttached(m_currentFocusElement.get())) {
865        FocusLog(Platform::LogLevelInfo, "InputHandler::setElementUnfocused");
866
867        // Pass any text into the field to IMF to learn.
868        learnText();
869
870        // End any composition that is in progress.
871        finishComposition();
872
873        // Only hide the keyboard if we aren't refocusing on a new input field.
874        if (!refocusOccuring) {
875            notifyClientOfKeyboardVisibilityChange(false, true /* triggeredByFocusChange */);
876            m_webPage->m_client->showFormControls(false /* visible */);
877        }
878
879        m_webPage->m_client->inputFocusLost();
880
881        // Hide the suggestion box if it is visible.
882        hideTextInputTypeSuggestionBox();
883
884        // Repaint the element absent of the caret.
885        if (m_currentFocusElement->renderer())
886            m_currentFocusElement->renderer()->repaint();
887
888        // If the frame selection isn't focused, focus it.
889        FrameSelection* frameSelection = m_currentFocusElement->document()->frame()->selection();
890        if (frameSelection && !frameSelection->isFocused())
891            frameSelection->setFocused(true);
892    }
893
894    // Cancel any preexisting spellcheck requests.
895    if (m_request) {
896        stopPendingSpellCheckRequests();
897        m_request->didCancel();
898        m_request = 0;
899    }
900
901    // Clear the node details.
902    m_currentFocusElement = 0;
903    m_currentFocusElementType = TextEdit;
904    m_previousFocusableTextElement = 0;
905    m_nextFocusableTextElement = 0;
906    m_hasSubmitButton = false;
907}
908
909bool InputHandler::isInputModeEnabled() const
910{
911    // Input mode is enabled when set, or when dump render tree or always show keyboard setting is enabled.
912    return m_inputModeEnabled || m_webPage->m_dumpRenderTree || Platform::Settings::instance()->alwaysShowKeyboardOnFocus();
913}
914
915void InputHandler::setInputModeEnabled(bool active)
916{
917    FocusLog(Platform::LogLevelInfo,
918        "InputHandler::setInputModeEnabled '%s', override is '%s'",
919        active ? "true" : "false",
920        m_webPage->m_dumpRenderTree || Platform::Settings::instance()->alwaysShowKeyboardOnFocus() ? "true" : "false");
921
922    m_inputModeEnabled = active;
923
924    // If the frame selection isn't focused, focus it.
925    if (isInputModeEnabled()
926        && isActiveTextEdit()
927        && DOMSupport::isElementAndDocumentAttached(m_currentFocusElement.get())
928        && !m_currentFocusElement->document()->frame()->selection()->isFocused())
929        m_currentFocusElement->document()->frame()->selection()->setFocused(true);
930}
931
932void InputHandler::updateFormState()
933{
934    m_previousFocusableTextElement = 0;
935    m_nextFocusableTextElement = 0;
936    m_hasSubmitButton = false;
937
938    if (!m_currentFocusElement || !m_currentFocusElement->isFormControlElement())
939        return;
940
941    HTMLFormElement* formElement = static_cast<HTMLFormControlElement*>(m_currentFocusElement.get())->form();
942    if (!formElement)
943        return;
944
945    const Vector<FormAssociatedElement*> formElementList = formElement->associatedElements();
946    int formElementCount = formElementList.size();
947    if (formElementCount < 2)
948        return;
949
950    m_hasSubmitButton = true;
951
952    // Walk all elements in the form to determine next/prev elements.
953    // For each element in the form we need to do the following:
954    // If it's the focus node, use render order to try to find the next/prev directly.
955    // For all other nodes:
956    // 1) If the focused node has a specific tab index, compare the elements tab
957    //    index with the current prev/next looking for the best match.
958    // 2) If the focused node does not have a tab index, update the maximum
959    //    tab index value, required for prev navigation.
960    int focusTabIndex = static_cast<Node*>(m_currentFocusElement.get())->tabIndex();
961    int prevTabIndex = -1;
962    int nextTabIndex = std::numeric_limits<short>::max() + 1;
963    InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState form has %d fields and tabIndex %d", formElementCount, focusTabIndex);
964
965    Element* firstInFieldWithoutTabIndex = 0;
966    Element* highestTabIndexElement = 0;
967    for (int focusElementId = 0; focusElementId < formElementCount; focusElementId++) {
968        // Check for the focused element, and if we don't have any target nodes, use the form order next
969        // and previous as placeholders. In a form without provided tab indices, this will determine the
970        // control fields.
971        Element* element = const_cast<HTMLElement*>(toHTMLElement(formElementList[focusElementId]));
972        if (element == m_currentFocusElement) {
973            InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found focused element.");
974
975            // If the focus tab index is set for the node, don't use the logical ordering.
976            // Jump from last tab index to un-ordered is done separately.
977            if (focusTabIndex)
978                continue;
979
980            // Get the next/prev element if we don't already have a tab index based item.
981            // Previous
982            if (!m_previousFocusableTextElement) {
983                for (int previousElementId = focusElementId - 1; previousElementId >= 0; previousElementId--) {
984                    Element* prevElement = const_cast<HTMLElement*>(toHTMLElement(formElementList[previousElementId]));
985                    if (DOMSupport::isTextBasedContentEditableElement(prevElement) && !DOMSupport::isElementReadOnly(prevElement) && !static_cast<Node*>(prevElement)->tabIndex()) {
986                        m_previousFocusableTextElement = prevElement;
987                        InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found previous element");
988                        break;
989                    }
990                }
991            }
992
993            // Next
994            if (!m_nextFocusableTextElement) {
995                for (int nextElementId = focusElementId + 1; nextElementId < formElementCount; nextElementId++) {
996                    Element* nextElement = const_cast<HTMLElement*>(toHTMLElement(formElementList[nextElementId]));
997                    if (DOMSupport::isTextBasedContentEditableElement(nextElement) && !DOMSupport::isElementReadOnly(nextElement) && !static_cast<Node*>(nextElement)->tabIndex()) {
998                        m_nextFocusableTextElement = nextElement;
999                        InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found next element");
1000                        break;
1001                    }
1002                }
1003            }
1004        } else if (focusTabIndex) {
1005            InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState processing element");
1006            if (DOMSupport::isTextBasedContentEditableElement(element) && !DOMSupport::isElementReadOnly(element)) {
1007                InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState processing element valid");
1008                if (int tabIndex = static_cast<Node*>(element)->tabIndex()) {
1009                    InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState processing element with tab index %d", tabIndex);
1010                    // Compare for the before and after form positions based on the tab index, and/or form position
1011                    // if tab indexes are equal form position should be used.
1012                    if (tabIndex && tabIndex < focusTabIndex && tabIndex > prevTabIndex) {
1013                        m_previousFocusableTextElement = element;
1014                        prevTabIndex = tabIndex;
1015                        InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found previous element with tabIndex %d", tabIndex);
1016                    } else if (tabIndex > focusTabIndex && tabIndex < nextTabIndex) {
1017                        m_nextFocusableTextElement = element;
1018                        nextTabIndex = tabIndex;
1019                        InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found next element with tabIndex %d", tabIndex);
1020                    }
1021                } else if (!firstInFieldWithoutTabIndex) {
1022                    // Store the first field in the form without a tab index if we have a form with some tab indexes the "next" one from
1023                    // the highest tab index is the first field without a tab index.
1024                    firstInFieldWithoutTabIndex = element;
1025                }
1026            }
1027        } else {
1028            // The field has no tab index, if it's the first node we'll need the highest tab index field
1029            // when navigating backwards.
1030            if (int tabIndex = static_cast<Node*>(element)->tabIndex()) {
1031                if (!highestTabIndexElement || (tabIndex > static_cast<Node*>(highestTabIndexElement)->tabIndex()))
1032                    highestTabIndexElement = element;
1033            }
1034        }
1035    }
1036
1037    if (!m_nextFocusableTextElement && firstInFieldWithoutTabIndex) {
1038        // No next focusable field was found, but a first one without a tabindex was found, use it as the next field.
1039        m_nextFocusableTextElement = firstInFieldWithoutTabIndex;
1040    }
1041
1042    if (!m_previousFocusableTextElement && highestTabIndexElement) {
1043        // No prev focusable field was found, use the highest tab index as previous since this field must not have
1044        // a tabindex, otheriwse highestTabIndexElement would be null.
1045        m_previousFocusableTextElement = highestTabIndexElement;
1046    }
1047
1048    if (!m_nextFocusableTextElement && !m_previousFocusableTextElement) {
1049        m_hasSubmitButton = false;
1050        InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState no valid elements found, clearing state.");
1051    }
1052}
1053
1054void InputHandler::focusNextField()
1055{
1056    if (!m_nextFocusableTextElement)
1057        return;
1058
1059    m_nextFocusableTextElement->focus();
1060}
1061
1062void InputHandler::focusPreviousField()
1063{
1064    if (!m_previousFocusableTextElement)
1065        return;
1066
1067    m_previousFocusableTextElement->focus();
1068}
1069
1070void InputHandler::submitForm()
1071{
1072    if (!m_hasSubmitButton)
1073        return;
1074
1075    HTMLFormElement* formElement = static_cast<HTMLFormControlElement*>(m_currentFocusElement.get())->form();
1076    if (!formElement)
1077        return;
1078
1079    InputLog(Platform::LogLevelInfo, "InputHandler::submitForm triggered");
1080
1081    if (elementType(m_currentFocusElement.get()) != InputTypeTextArea) {
1082        handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_RETURN, Platform::KeyboardEvent::KeyChar, 0), false /* changeIsPartOfComposition */);
1083
1084        // Did this clear the focus? If so, form was submitted or invalid.
1085        if (!isActiveTextEdit())
1086            return;
1087    }
1088
1089    // Validate form data and if valid, submit.
1090    if (formElement->checkValidity())
1091        formElement->submit();
1092}
1093
1094static void addInputStyleMaskForKeyboardType(int64_t& inputMask, VirtualKeyboardType keyboardType)
1095{
1096    switch (keyboardType) {
1097    case VKBTypeUrl:
1098        inputMask |= IMF_URL_TYPE;
1099        break;
1100    case VKBTypePassword:
1101        inputMask |= IMF_PASSWORD_TYPE;
1102        break;
1103    case VKBTypePin:
1104        inputMask |= IMF_PIN_TYPE;
1105        break;
1106    case VKBTypePhone:
1107        inputMask |= IMF_PHONE_TYPE;
1108        break;
1109    case VKBTypeEmail:
1110        inputMask |= IMF_EMAIL_TYPE;
1111        break;
1112    default:
1113        break;
1114    }
1115}
1116
1117void InputHandler::setElementFocused(Element* element)
1118{
1119    ASSERT(DOMSupport::isTextBasedContentEditableElement(element));
1120    ASSERT(element && element->document() && element->document()->frame());
1121
1122#if ENABLE_SPELLING_LOG
1123    BlackBerry::Platform::StopWatch timer;
1124    timer.start();
1125#endif
1126
1127    if (!element || !(element->document()))
1128        return;
1129
1130    Frame* frame = element->document()->frame();
1131    if (!frame)
1132        return;
1133
1134    if (frame->selection()->isFocused() != isInputModeEnabled())
1135        frame->selection()->setFocused(isInputModeEnabled());
1136
1137    // Ensure visible when refocusing.
1138    // If device does not have physical keyboard, wait to ensure visible until VKB resizes viewport so that both animations are combined into one.
1139    m_shouldEnsureFocusTextElementVisibleOnSelectionChanged = isActiveTextEdit() || DeviceInfo::instance()->hasPhysicalKeyboard();
1140
1141    // Clear the existing focus node details.
1142    setElementUnfocused(true /*refocusOccuring*/);
1143
1144    // Mark this element as active and add to frame set.
1145    m_currentFocusElement = element;
1146    m_currentFocusElementType = TextEdit;
1147    updateFormState();
1148
1149    if (isInputModeEnabled() && !m_delayKeyboardVisibilityChange)
1150        m_webPage->m_client->showFormControls(m_hasSubmitButton /* visible */, m_previousFocusableTextElement, m_nextFocusableTextElement);
1151    else
1152        m_sendFormStateOnNextKeyboardRequest = true;
1153
1154    // Send details to the client about this element.
1155    BlackBerryInputType type = elementType(element);
1156    m_currentFocusElementTextEditMask = inputStyle(type, element);
1157
1158    VirtualKeyboardType keyboardType = keyboardTypeAttribute(element);
1159    if (keyboardType == VKBTypeNotSet)
1160        keyboardType = convertInputTypeToVKBType(type);
1161
1162    addInputStyleMaskForKeyboardType(m_currentFocusElementTextEditMask, keyboardType);
1163
1164    VirtualKeyboardEnterKeyType enterKeyType = keyboardEnterKeyTypeAttribute(element);
1165
1166    if (enterKeyType == VKBEnterKeyNotSet && type != InputTypeTextArea) {
1167        if (element->isFormControlElement()) {
1168            const HTMLFormControlElement* formElement = static_cast<const HTMLFormControlElement*>(element);
1169            if (formElement->form() && formElement->form()->defaultButton())
1170                enterKeyType = VKBEnterKeySubmit;
1171        }
1172    }
1173
1174    FocusLog(Platform::LogLevelInfo,
1175        "InputHandler::setElementFocused, Type=%d, Style=%lld, Keyboard Type=%d, Enter Key=%d",
1176        type, m_currentFocusElementTextEditMask, keyboardType, enterKeyType);
1177
1178    m_webPage->m_client->inputFocusGained(m_currentFocusElementTextEditMask, keyboardType, enterKeyType);
1179
1180    handleInputLocaleChanged(m_webPage->m_webSettings->isWritingDirectionRTL());
1181
1182    // update the suggestion box
1183    showTextInputTypeSuggestionBox();
1184
1185    if (!m_delayKeyboardVisibilityChange)
1186        notifyClientOfKeyboardVisibilityChange(true, true /* triggeredByFocusChange */);
1187
1188#if ENABLE_SPELLING_LOG
1189    SpellingLog(Platform::LogLevelInfo, "InputHandler::setElementFocused Focusing the field took %f seconds.", timer.elapsed());
1190#endif
1191
1192    // Spellcheck the field in its entirety.
1193    spellCheckTextBlock(element);
1194
1195#if ENABLE_SPELLING_LOG
1196    SpellingLog(Platform::LogLevelInfo, "InputHandler::setElementFocused Spellchecking the field increased the total time to focus to %f seconds.", timer.elapsed());
1197#endif
1198}
1199
1200void InputHandler::spellCheckTextBlock(Element* element)
1201{
1202    SpellingLog(Platform::LogLevelInfo, "InputHandler::spellCheckTextBlock");
1203
1204    if (!element) {
1205        // Fall back to a valid focused element.
1206        if (!m_currentFocusElement)
1207            return;
1208
1209        element = m_currentFocusElement.get();
1210    }
1211
1212    // Check if the field should be spellchecked.
1213    if (!shouldSpellCheckElement(element) || !isActiveTextEdit())
1214        return;
1215
1216    m_spellingHandler->spellCheckTextBlock(element, TextCheckingProcessBatch);
1217}
1218
1219bool InputHandler::shouldSpellCheckElement(const Element* element) const
1220{
1221    DOMSupport::AttributeState spellCheckAttr = DOMSupport::elementSupportsSpellCheck(element);
1222
1223    // Explicitly set to off.
1224    if (spellCheckAttr == DOMSupport::Off)
1225        return false;
1226
1227    // Undefined and part of a set of cases which we do not wish to check. This includes user names and email addresses, so we are piggybacking on NoAutocomplete cases.
1228    if (spellCheckAttr == DOMSupport::Default && (m_currentFocusElementTextEditMask & NO_AUTO_TEXT))
1229        return false;
1230
1231    // Check if the system spell check setting is off
1232    return m_spellCheckStatusConfirmed ? m_globalSpellCheckStatus : true;
1233}
1234
1235void InputHandler::stopPendingSpellCheckRequests(bool isRestartRequired)
1236{
1237    m_spellingHandler->setSpellCheckActive(false);
1238    // Prevent response from propagating through.
1239    m_processingTransactionId = 0;
1240
1241    // Reject requests until lastRequestSequence. This helps us clear the queue of stale requests.
1242    if (SpellChecker* spellChecker = getSpellChecker()) {
1243        if (spellChecker->lastRequestSequence() != spellChecker->lastProcessedSequence()) {
1244            SpellingLog(LogLevelInfo, "InputHandler::stopPendingSpellCheckRequests will block requests up to lastRequest=%d [lastProcessed=%d]"
1245                , spellChecker->lastRequestSequence(), spellChecker->lastProcessedSequence());
1246            // Prevent requests in queue from executing.
1247            m_minimumSpellCheckingRequestSequence = spellChecker->lastRequestSequence();
1248            if (isRestartRequired && !compositionActive()) {
1249                // Create new spellcheck requests to replace those that were invalidated.
1250                spellCheckTextBlock();
1251            }
1252        }
1253    }
1254}
1255
1256void InputHandler::redrawSpellCheckDialogIfRequired(const bool shouldMoveDialog)
1257{
1258    if (didSpellCheckWord()) {
1259        imf_sp_text_t spellCheckingOptionRequest;
1260        spellCheckingOptionRequest.startTextPosition = 0;
1261        spellCheckingOptionRequest.endTextPosition = 0;
1262        WebCore::IntSize screenOffset(-1, -1);
1263        requestSpellingCheckingOptions(spellCheckingOptionRequest, screenOffset, shouldMoveDialog);
1264    }
1265}
1266
1267bool InputHandler::openDatePopup(HTMLInputElement* element, BlackBerryInputType type)
1268{
1269    if (!element || element->isDisabledOrReadOnly() || !DOMSupport::isDateTimeInputField(element))
1270        return false;
1271
1272    if (isActiveTextEdit())
1273        clearCurrentFocusElement();
1274
1275    m_currentFocusElement = element;
1276    m_currentFocusElementType = TextPopup;
1277
1278    switch (type) {
1279    case BlackBerry::Platform::InputTypeDate:
1280    case BlackBerry::Platform::InputTypeTime:
1281    case BlackBerry::Platform::InputTypeDateTime:
1282    case BlackBerry::Platform::InputTypeDateTimeLocal:
1283    case BlackBerry::Platform::InputTypeMonth: {
1284        // Date input have button appearance, we hide caret when they get clicked.
1285        element->document()->frame()->selection()->setCaretVisible(false);
1286
1287        WTF::String value = element->value();
1288        WTF::String min = element->getAttribute(HTMLNames::minAttr).string();
1289        WTF::String max = element->getAttribute(HTMLNames::maxAttr).string();
1290        double step = element->getAttribute(HTMLNames::stepAttr).toDouble();
1291
1292        DatePickerClient* client = new DatePickerClient(type, value, min, max, step,  m_webPage, element);
1293        return m_webPage->openPagePopup(client,  WebCore::IntRect());
1294        }
1295    default: // Other types not supported
1296        return false;
1297    }
1298}
1299
1300bool InputHandler::openColorPopup(HTMLInputElement* element)
1301{
1302    if (!element || element->isDisabledOrReadOnly() || !DOMSupport::isColorInputField(element))
1303        return false;
1304
1305    if (isActiveTextEdit())
1306        clearCurrentFocusElement();
1307
1308    m_currentFocusElement = element;
1309    m_currentFocusElementType = TextPopup;
1310
1311    ColorPickerClient* client = new ColorPickerClient(element->value(), m_webPage, element);
1312    return m_webPage->openPagePopup(client, WebCore::IntRect());
1313}
1314
1315void InputHandler::setInputValue(const WTF::String& value)
1316{
1317    if (!isActiveTextPopup())
1318        return;
1319
1320    HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(m_currentFocusElement.get());
1321    inputElement->setValue(value);
1322    clearCurrentFocusElement();
1323}
1324
1325void InputHandler::nodeTextChanged(const Node* node)
1326{
1327    if (processingChange() || !node || node != m_currentFocusElement || !m_shouldNotifyWebView)
1328        return;
1329
1330    InputLog(Platform::LogLevelInfo, "InputHandler::nodeTextChanged");
1331
1332    m_webPage->m_client->inputTextChanged();
1333
1334    // Remove the attributed text markers as the previous call triggered an end to
1335    // the composition.
1336    removeAttributedTextMarker();
1337}
1338
1339WebCore::IntRect InputHandler::boundingBoxForInputField()
1340{
1341    if (!isActiveTextEdit())
1342        return WebCore::IntRect();
1343
1344    if (!m_currentFocusElement->renderer())
1345        return WebCore::IntRect();
1346
1347    // type="search" can have a 'X', so take the inner block bounding box to not include it.
1348    if (HTMLInputElement* element = m_currentFocusElement->toInputElement()) {
1349        if (element->isSearchField())
1350            return element->innerBlockElement()->renderer()->absoluteBoundingBoxRect();
1351        return m_currentFocusElement->renderer()->absoluteBoundingBoxRect();
1352    }
1353
1354    if (m_currentFocusElement->hasTagName(HTMLNames::textareaTag))
1355        return m_currentFocusElement->renderer()->absoluteBoundingBoxRect();
1356
1357    // Content Editable can't rely on the bounding box since it isn't fixed.
1358    return WebCore::IntRect();
1359}
1360
1361void InputHandler::ensureFocusTextElementVisible(CaretScrollType scrollType)
1362{
1363    if (!isActiveTextEdit() || !isInputModeEnabled() || !m_currentFocusElement->document())
1364        return;
1365
1366    if (!(Platform::Settings::instance()->allowedScrollAdjustmentForInputFields() & scrollType))
1367        return;
1368
1369    // Fixed position elements cannot be scrolled into view.
1370    if (DOMSupport::isFixedPositionOrHasFixedPositionAncestor(m_currentFocusElement->renderer()))
1371        return;
1372
1373    Frame* elementFrame = m_currentFocusElement->document()->frame();
1374    if (!elementFrame)
1375        return;
1376
1377    Frame* mainFrame = m_webPage->mainFrame();
1378    if (!mainFrame)
1379        return;
1380
1381    FrameView* mainFrameView = mainFrame->view();
1382    if (!mainFrameView)
1383        return;
1384
1385    WebCore::IntRect selectionFocusRect;
1386    switch (elementFrame->selection()->selectionType()) {
1387    case VisibleSelection::CaretSelection:
1388        selectionFocusRect = elementFrame->selection()->absoluteCaretBounds();
1389        break;
1390    case VisibleSelection::RangeSelection: {
1391        Position selectionPosition;
1392        if (m_webPage->m_selectionHandler->lastUpdatedEndPointIsValid())
1393            selectionPosition = elementFrame->selection()->end();
1394        else
1395            selectionPosition = elementFrame->selection()->start();
1396        selectionFocusRect = VisiblePosition(selectionPosition).absoluteCaretBounds();
1397        break;
1398    }
1399    case VisibleSelection::NoSelection:
1400        m_shouldEnsureFocusTextElementVisibleOnSelectionChanged = true;
1401        return;
1402    }
1403
1404    int fontHeight = selectionFocusRect.height();
1405
1406    // If the text is too small, zoom in to make it a minimum size.
1407    // The minimum size being defined as 3 mm is a good value based on my observations.
1408    static const int s_minimumTextHeightInPixels = Graphics::Screen::primaryScreen()->heightInMMToPixels(3);
1409
1410    double zoomScaleRequired;
1411    if (m_webPage->isUserScalable() && fontHeight && fontHeight * m_webPage->currentScale() < s_minimumTextHeightInPixels && !isRunningDrt())
1412        zoomScaleRequired = static_cast<double>(s_minimumTextHeightInPixels) / fontHeight;
1413    else
1414        zoomScaleRequired = m_webPage->currentScale(); // Don't scale.
1415
1416    // Zoom level difference must exceed the given threshold before we perform a zoom animation.
1417    if (abs(zoomScaleRequired - m_webPage->currentScale()) < zoomAnimationThreshold)
1418        zoomScaleRequired = m_webPage->currentScale(); // Don't scale.
1419
1420    // The scroll location we should go to given the zoom required, could be adjusted later.
1421    WebCore::FloatPoint offset(selectionFocusRect.location().x() - m_webPage->scrollPosition().x(), selectionFocusRect.location().y() - m_webPage->scrollPosition().y());
1422    double inverseScale = zoomScaleRequired / m_webPage->currentScale();
1423    WebCore::IntPoint destinationScrollLocation = WebCore::IntPoint(
1424        max(0, static_cast<int>(roundf(selectionFocusRect.location().x() - offset.x() / inverseScale))),
1425        max(0, static_cast<int>(roundf(selectionFocusRect.location().y() - offset.y() / inverseScale))));
1426
1427    if (elementFrame != mainFrame) { // Element is in a subframe.
1428        // Remove any scroll offset within the subframe to get the point relative to the main frame.
1429        selectionFocusRect.move(-elementFrame->view()->scrollPosition().x(), -elementFrame->view()->scrollPosition().y());
1430
1431        // Adjust the selection rect based on the frame offset in relation to the main frame if it's a subframe.
1432        if (elementFrame->ownerRenderer()) {
1433            WebCore::IntPoint frameOffset = elementFrame->ownerRenderer()->absoluteContentBox().location();
1434            selectionFocusRect.move(frameOffset.x(), frameOffset.y());
1435        }
1436    }
1437
1438    const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor;
1439    if (scrollType == EdgeIfNeeded
1440        && (viewportAccessor->documentViewportRect().contains(selectionFocusRect))
1441        && zoomScaleRequired == m_webPage->currentScale()) {
1442        // Already in view and no zoom is required, return early.
1443        return;
1444    }
1445
1446    bool shouldConstrainScrollingToContentEdge = true;
1447    Position start = elementFrame->selection()->start();
1448    if (start.anchorNode() && start.anchorNode()->renderer()) {
1449        if (RenderLayer* layer = start.anchorNode()->renderer()->enclosingLayer()) {
1450            // Screen rect after the required zoom.
1451            WebCore::IntRect actualScreenRect = WebCore::IntRect(destinationScrollLocation.x(), destinationScrollLocation.y(), m_webPage->actualVisibleSize().width() / inverseScale, m_webPage->actualVisibleSize().height() / inverseScale);
1452
1453            ScrollAlignment horizontalScrollAlignment = ScrollAlignment::alignToEdgeIfNeeded;
1454            ScrollAlignment verticalScrollAlignment = ScrollAlignment::alignToEdgeIfNeeded;
1455
1456            if (scrollType != EdgeIfNeeded) {
1457                // Align the selection rect if possible so that we show the field's
1458                // outline if the caret is at the edge of the field.
1459                if (RenderObject* focusedRenderer = m_currentFocusElement->renderer()) {
1460                    WebCore::IntRect nodeOutlineBounds = focusedRenderer->absoluteOutlineBounds();
1461                    WebCore::IntRect caretAtEdgeRect = rectForCaret(0);
1462                    int paddingX = abs(caretAtEdgeRect.x() - nodeOutlineBounds.x());
1463                    int paddingY = abs(caretAtEdgeRect.y() - nodeOutlineBounds.y());
1464
1465                    if (selectionFocusRect.x() - paddingX == nodeOutlineBounds.x())
1466                        selectionFocusRect.setX(nodeOutlineBounds.x());
1467                    else if (selectionFocusRect.maxX() + paddingX == nodeOutlineBounds.maxX())
1468                        selectionFocusRect.setX(nodeOutlineBounds.maxX() - selectionFocusRect.width());
1469                    if (selectionFocusRect.y() - paddingY == nodeOutlineBounds.y())
1470                        selectionFocusRect.setY(nodeOutlineBounds.y() - selectionFocusRect.height());
1471                    else if (selectionFocusRect.maxY() + paddingY == nodeOutlineBounds.maxY())
1472                        selectionFocusRect.setY(nodeOutlineBounds.maxY() - selectionFocusRect.height());
1473
1474                    // If the editing point is on the left hand side of the screen when the node's
1475                    // rect is edge aligned, edge align the node rect.
1476                    if (selectionFocusRect.x() - caretAtEdgeRect.x() < actualScreenRect.width() / 2)
1477                        selectionFocusRect.setX(nodeOutlineBounds.x());
1478                    else
1479                        horizontalScrollAlignment = ScrollAlignment::alignCenterIfNeeded;
1480
1481                }
1482                verticalScrollAlignment = (scrollType == CenterAlways) ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded;
1483            }
1484
1485            // Pad the rect to improve the visual appearance.
1486            // Convert the padding back from transformed to ensure a consistent padding regardless of
1487            // zoom level as controls do not zoom.
1488            static const int s_focusRectPaddingSize = Graphics::Screen::primaryScreen()->heightInMMToPixels(12);
1489            selectionFocusRect.inflate(std::ceilf(viewportAccessor->documentFromPixelContents(Platform::FloatSize(0, s_focusRectPaddingSize)).height()));
1490
1491            WebCore::IntRect revealRect(layer->getRectToExpose(actualScreenRect, selectionFocusRect, horizontalScrollAlignment, verticalScrollAlignment));
1492
1493            // Don't constrain scroll position when animation finishes.
1494            shouldConstrainScrollingToContentEdge = false;
1495
1496            // In order to adjust the scroll position to ensure the focused input field is visible,
1497            // we allow overscrolling. However this overscroll has to be strictly allowed towards the
1498            // bottom of the page on the y axis only, where the virtual keyboard pops up from.
1499            destinationScrollLocation = revealRect.location();
1500            destinationScrollLocation.clampNegativeToZero();
1501            WebCore::IntPoint maximumScrollPosition = WebCore::IntPoint(mainFrameView->contentsWidth() - actualScreenRect.width(), mainFrameView->contentsHeight() - actualScreenRect.height());
1502            destinationScrollLocation = destinationScrollLocation.shrunkTo(maximumScrollPosition);
1503        }
1504    }
1505
1506    InputLog(Platform::LogLevelInfo,
1507        "InputHandler::ensureFocusTextElementVisible zooming in to %f from %f and scrolling to point %s from %s",
1508        zoomScaleRequired, m_webPage->currentScale(),
1509        Platform::IntPoint(destinationScrollLocation).toString().c_str(),
1510        Platform::IntPoint(mainFrameView->scrollPosition()).toString().c_str());
1511
1512    m_webPage->animateToScaleAndDocumentScrollPosition(zoomScaleRequired, WebCore::FloatPoint(destinationScrollLocation), shouldConstrainScrollingToContentEdge);
1513}
1514
1515void InputHandler::ensureFocusPluginElementVisible()
1516{
1517    if (!isActivePlugin() || !m_currentFocusElement->document())
1518        return;
1519
1520    Frame* elementFrame = m_currentFocusElement->document()->frame();
1521    if (!elementFrame)
1522        return;
1523
1524    Frame* mainFrame = m_webPage->mainFrame();
1525    if (!mainFrame)
1526        return;
1527
1528    FrameView* mainFrameView = mainFrame->view();
1529    if (!mainFrameView)
1530        return;
1531
1532    WebCore::IntRect selectionFocusRect;
1533
1534    RenderWidget* renderWidget = static_cast<RenderWidget*>(m_currentFocusElement->renderer());
1535    if (renderWidget) {
1536        PluginView* pluginView = toPluginView(renderWidget->widget());
1537
1538        if (pluginView)
1539            selectionFocusRect = pluginView->ensureVisibleRect();
1540    }
1541
1542    if (selectionFocusRect.isEmpty())
1543        return;
1544
1545    // FIXME: We may need to scroll the subframe (recursively) in the future. Revisit this...
1546    if (elementFrame != mainFrame) { // Element is in a subframe.
1547        // Remove any scroll offset within the subframe to get the point relative to the main frame.
1548        selectionFocusRect.move(-elementFrame->view()->scrollPosition().x(), -elementFrame->view()->scrollPosition().y());
1549
1550        // Adjust the selection rect based on the frame offset in relation to the main frame if it's a subframe.
1551        if (elementFrame->ownerRenderer()) {
1552            WebCore::IntPoint frameOffset = elementFrame->ownerRenderer()->absoluteContentBox().location();
1553            selectionFocusRect.move(frameOffset.x(), frameOffset.y());
1554        }
1555    }
1556
1557    WebCore::IntRect actualScreenRect = WebCore::IntRect(mainFrameView->scrollPosition(), m_webPage->actualVisibleSize());
1558    if (actualScreenRect.contains(selectionFocusRect))
1559        return;
1560
1561    // Calculate a point such that the center of the requested rectangle
1562    // is at the center of the screen. FIXME: If the element was partially on screen
1563    // we might want to just bring the offscreen portion into view, someone needs
1564    // to decide if that's the behavior we want or not.
1565    WebCore::IntPoint pos(selectionFocusRect.center());
1566    pos.move(-actualScreenRect.width() / 2, -actualScreenRect.height() / 2);
1567
1568    mainFrameView->setScrollPosition(pos);
1569}
1570
1571void InputHandler::ensureFocusElementVisible(bool centerInView)
1572{
1573    if (isActivePlugin())
1574        ensureFocusPluginElementVisible();
1575    else
1576        ensureFocusTextElementVisible(centerInView ? CenterAlways : CenterIfNeeded);
1577}
1578
1579void InputHandler::frameUnloaded(const Frame* frame)
1580{
1581    if (!isActiveTextEdit())
1582        return;
1583
1584    if (m_currentFocusElement->document()->frame() != frame)
1585        return;
1586
1587    FocusLog(Platform::LogLevelInfo, "InputHandler::frameUnloaded");
1588
1589    setElementUnfocused(false /*refocusOccuring*/);
1590}
1591
1592void InputHandler::setDelayKeyboardVisibilityChange(bool value)
1593{
1594    m_delayKeyboardVisibilityChange = value;
1595    m_pendingKeyboardVisibilityChange = NoChange;
1596}
1597
1598void InputHandler::processPendingKeyboardVisibilityChange()
1599{
1600    if (!m_delayKeyboardVisibilityChange) {
1601        ASSERT(m_pendingKeyboardVisibilityChange == NoChange);
1602        return;
1603    }
1604
1605    m_delayKeyboardVisibilityChange = false;
1606
1607    if (m_pendingKeyboardVisibilityChange == NoChange)
1608        return;
1609
1610    notifyClientOfKeyboardVisibilityChange(m_pendingKeyboardVisibilityChange == Visible);
1611    m_pendingKeyboardVisibilityChange = NoChange;
1612}
1613
1614void InputHandler::notifyClientOfKeyboardVisibilityChange(bool visible, bool triggeredByFocusChange)
1615{
1616    // If we aren't ready for input, keyboard changes should be ignored.
1617    if (!isInputModeEnabled() && visible)
1618        return;
1619
1620    // If we are processing a change assume the keyboard is visbile to avoid
1621    // flooding the VKB with show requests.
1622    if (!triggeredByFocusChange && processingChange() && visible)
1623        return;
1624
1625    if (!m_delayKeyboardVisibilityChange) {
1626        if (m_sendFormStateOnNextKeyboardRequest) {
1627            m_webPage->m_client->showFormControls(m_hasSubmitButton /* visible */, m_previousFocusableTextElement, m_nextFocusableTextElement);
1628            m_sendFormStateOnNextKeyboardRequest = false;
1629        }
1630        m_webPage->showVirtualKeyboard(visible);
1631        return;
1632    }
1633
1634    m_pendingKeyboardVisibilityChange = visible ? Visible : NotVisible;
1635}
1636
1637bool InputHandler::selectionAtStartOfElement()
1638{
1639    if (!isActiveTextEdit())
1640        return false;
1641
1642    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1643
1644    if (!selectionStart())
1645        return true;
1646
1647    return false;
1648}
1649
1650bool InputHandler::selectionAtEndOfElement()
1651{
1652    if (!isActiveTextEdit())
1653        return false;
1654
1655    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1656
1657    return selectionStart() == static_cast<int>(elementText().length());
1658}
1659
1660int InputHandler::selectionStart() const
1661{
1662    return selectionPosition(true);
1663}
1664
1665int InputHandler::selectionEnd() const
1666{
1667    return selectionPosition(false);
1668}
1669
1670int InputHandler::selectionPosition(bool start) const
1671{
1672    if (!m_currentFocusElement->document() || !m_currentFocusElement->document()->frame())
1673        return 0;
1674
1675    if (HTMLTextFormControlElement* controlElement = DOMSupport::toTextControlElement(m_currentFocusElement.get()))
1676        return start ? controlElement->selectionStart() : controlElement->selectionEnd();
1677
1678    FrameSelection caretSelection;
1679    caretSelection.setSelection(m_currentFocusElement->document()->frame()->selection()->selection());
1680    RefPtr<Range> rangeSelection = caretSelection.selection().toNormalizedRange();
1681    if (!rangeSelection)
1682        return 0;
1683
1684    int selectionPointInNode = start ? rangeSelection->startOffset() : rangeSelection->endOffset();
1685    Node* containerNode = start ? rangeSelection->startContainer() : rangeSelection->endContainer();
1686
1687    ExceptionCode ec;
1688    RefPtr<Range> rangeForNode = rangeOfContents(m_currentFocusElement.get());
1689    rangeForNode->setEnd(containerNode, selectionPointInNode, ec);
1690    ASSERT(!ec);
1691
1692    return TextIterator::rangeLength(rangeForNode.get());
1693}
1694
1695void InputHandler::selectionChanged()
1696{
1697    // This method can get called during WebPage shutdown process.
1698    // If that is the case, just bail out since the client is not
1699    // in a safe state of trust to request anything else from it.
1700    if (!m_webPage->m_mainFrame)
1701        return;
1702
1703    if (!isActiveTextEdit())
1704        return;
1705
1706    if (processingChange()) {
1707        m_webPage->m_client->suppressCaretChangeNotification(true /*shouldClearState*/);
1708        return;
1709    }
1710
1711    // Scroll the field if necessary. This must be done even if we are processing
1712    // a change as the text change may have moved the caret. IMF doesn't require
1713    // the update, but the user needs to see the caret.
1714    if (m_shouldEnsureFocusTextElementVisibleOnSelectionChanged) {
1715        ensureFocusTextElementVisible(EdgeIfNeeded);
1716        m_shouldEnsureFocusTextElementVisibleOnSelectionChanged = false;
1717    }
1718
1719    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1720
1721    if (!m_shouldNotifyWebView)
1722        return;
1723
1724    int newSelectionStart = selectionStart();
1725    int newSelectionEnd = selectionEnd();
1726
1727    InputLog(Platform::LogLevelInfo,
1728        "InputHandler::selectionChanged selectionStart=%u, selectionEnd=%u",
1729        newSelectionStart, newSelectionEnd);
1730
1731    m_webPage->m_client->inputSelectionChanged(newSelectionStart, newSelectionEnd);
1732
1733    // Remove the attributed text markers as the previous call triggered an end to
1734    // the composition.
1735    removeAttributedTextMarker();
1736}
1737
1738bool InputHandler::setCursorPosition(int location)
1739{
1740    return setSelection(location, location);
1741}
1742
1743bool InputHandler::setSelection(int start, int end, bool changeIsPartOfComposition)
1744{
1745    if (!isActiveTextEdit())
1746        return false;
1747
1748    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1749
1750    ProcessingChangeGuard guard(this);
1751
1752    VisibleSelection newSelection = DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), start, end);
1753    m_currentFocusElement->document()->frame()->selection()->setSelection(newSelection, changeIsPartOfComposition ? 0 : FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle);
1754
1755    InputLog(Platform::LogLevelInfo,
1756        "InputHandler::setSelection selectionStart=%u, selectionEnd=%u",
1757        start, end);
1758
1759    return start == selectionStart() && end == selectionEnd();
1760}
1761
1762WebCore::IntRect InputHandler::rectForCaret(int index)
1763{
1764    if (!isActiveTextEdit())
1765        return WebCore::IntRect();
1766
1767    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1768
1769    if (index < 0 || index > static_cast<int>(elementText().length())) {
1770        // Invalid request.
1771        return WebCore::IntRect();
1772    }
1773
1774    FrameSelection caretSelection;
1775    caretSelection.setSelection(DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), index, index).visibleStart());
1776    caretSelection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
1777    return caretSelection.selection().visibleStart().absoluteCaretBounds();
1778}
1779
1780void InputHandler::cancelSelection()
1781{
1782    if (!isActiveTextEdit())
1783        return;
1784
1785    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1786
1787    int selectionStartPosition = selectionStart();
1788    ProcessingChangeGuard guard(this);
1789    setCursorPosition(selectionStartPosition);
1790}
1791
1792bool InputHandler::isNavigationKey(unsigned character) const
1793{
1794    return character == KEYCODE_UP
1795        || character == KEYCODE_DOWN
1796        || character == KEYCODE_LEFT
1797        || character == KEYCODE_RIGHT;
1798}
1799
1800bool InputHandler::handleKeyboardInput(const Platform::KeyboardEvent& keyboardEvent, bool changeIsPartOfComposition)
1801{
1802    InputLog(Platform::LogLevelInfo,
1803        "InputHandler::handleKeyboardInput received character='%c', type=%d",
1804        keyboardEvent.character(), keyboardEvent.type());
1805
1806    // Clearing the m_shouldNotifyWebView state on any KeyboardEvent.
1807    m_shouldNotifyWebView = true;
1808
1809    // Enable input mode if we are processing a key event.
1810    setInputModeEnabled();
1811
1812    Platform::KeyboardEvent::Type type = keyboardEvent.type();
1813    /*
1814     * IMF sends us an unadultered KeyUp for all key presses. This key event should be allowed to be processed at all times.
1815     * We bypass the check because the state of composition has no implication on this key event.
1816     * In order to ensure we allow the correct key event through, we keep track of key down events with m_expectedKeyUpChar.
1817    */
1818    if (type == Platform::KeyboardEvent::KeyUp) {
1819        // When IMF auto-capitalizes a KeyDown, say the first letter of a new sentence, our KeyUp will still be in lowercase.
1820        if (m_expectedKeyUpChar == keyboardEvent.character() || (isASCIIUpper(m_expectedKeyUpChar) && m_expectedKeyUpChar == toASCIIUpper(keyboardEvent.character()))) {
1821            m_expectedKeyUpChar = 0;
1822            changeIsPartOfComposition = true;
1823        }
1824    }
1825
1826    // If we aren't specifically part of a composition, fail, IMF should never send key input
1827    // while composing text. If IMF has failed, we should have already finished the
1828    // composition manually. There is a caveat for KeyUp which is explained above.
1829    if (!changeIsPartOfComposition && compositionActive()) {
1830        if (type == Platform::KeyboardEvent::KeyDown && isNavigationKey(keyboardEvent.character()))
1831            removeAttributedTextMarker();
1832        else
1833            return false;
1834    }
1835
1836    ProcessingChangeGuard guard(this);
1837
1838    unsigned adjustedModifiers = keyboardEvent.modifiers();
1839    if (WTF::isASCIIUpper(keyboardEvent.character()))
1840        adjustedModifiers |= KEYMOD_SHIFT;
1841
1842    ASSERT(m_webPage->m_page->focusController());
1843    bool keyboardEventHandled = false;
1844    if (Frame* focusedFrame = m_webPage->m_page->focusController()->focusedFrame()) {
1845        bool isKeyChar = type == Platform::KeyboardEvent::KeyChar;
1846
1847        // If this is a KeyChar type then we handle it as a keydown followed by a key up.
1848        if (isKeyChar)
1849            type = Platform::KeyboardEvent::KeyDown;
1850        else if (type == Platform::KeyboardEvent::KeyDown) {
1851            m_expectedKeyUpChar = keyboardEvent.character();
1852
1853            m_shouldNotifyWebView = shouldNotifyWebView(keyboardEvent);
1854        }
1855
1856        Platform::KeyboardEvent adjustedKeyboardEvent(keyboardEvent.character(), type, adjustedModifiers, keyboardEvent.keycode(), keyboardEvent.alternateCharacter(), keyboardEvent.sourceDevice());
1857        keyboardEventHandled = focusedFrame->eventHandler()->keyEvent(PlatformKeyboardEvent(adjustedKeyboardEvent));
1858
1859        m_shouldNotifyWebView = true;
1860
1861        if (isKeyChar) {
1862            type = Platform::KeyboardEvent::KeyUp;
1863            adjustedKeyboardEvent = Platform::KeyboardEvent(keyboardEvent.character(), type, adjustedModifiers, keyboardEvent.keycode(), keyboardEvent.alternateCharacter(), keyboardEvent.sourceDevice());
1864            keyboardEventHandled = focusedFrame->eventHandler()->keyEvent(PlatformKeyboardEvent(adjustedKeyboardEvent)) || keyboardEventHandled;
1865        }
1866
1867        if (!changeIsPartOfComposition && type == Platform::KeyboardEvent::KeyUp)
1868            ensureFocusTextElementVisible(EdgeIfNeeded);
1869    }
1870
1871    if (m_currentFocusElement && keyboardEventHandled)
1872        showTextInputTypeSuggestionBox();
1873
1874    return keyboardEventHandled;
1875}
1876
1877bool InputHandler::shouldNotifyWebView(const Platform::KeyboardEvent& keyboardEvent)
1878{
1879    // If we receive the KeyDown of a Backspace or Enter key, set this flag to prevent sending unnecessary selection and caret changes to IMF.
1880    return !(keyboardEvent.character() == KEYCODE_BACKSPACE || keyboardEvent.character() == KEYCODE_RETURN || keyboardEvent.character() == KEYCODE_KP_ENTER);
1881}
1882
1883bool InputHandler::deleteSelection()
1884{
1885    if (!isActiveTextEdit())
1886        return false;
1887
1888    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
1889    Frame* frame = m_currentFocusElement->document()->frame();
1890
1891    if (frame->selection()->selectionType() != VisibleSelection::RangeSelection)
1892        return false;
1893
1894    ASSERT(frame->editor());
1895    if (!handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), false /* changeIsPartOfComposition */))
1896        return false;
1897
1898    selectionChanged();
1899    return true;
1900}
1901
1902void InputHandler::insertText(const WTF::String& string)
1903{
1904    if (!isActiveTextEdit())
1905        return;
1906
1907    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->editor());
1908    Editor* editor = m_currentFocusElement->document()->frame()->editor();
1909    editor->command("InsertText").execute(string);
1910}
1911
1912void InputHandler::clearField()
1913{
1914    if (!isActiveTextEdit())
1915        return;
1916
1917    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->editor());
1918    Editor* editor = m_currentFocusElement->document()->frame()->editor();
1919
1920    editor->command("SelectAll").execute();
1921    editor->command("DeleteBackward").execute();
1922}
1923
1924bool InputHandler::executeTextEditCommand(const WTF::String& commandName)
1925{
1926    ASSERT(m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->editor());
1927    Editor* editor = m_webPage->focusedOrMainFrame()->editor();
1928
1929    return editor->command(commandName).execute();
1930}
1931
1932void InputHandler::cut()
1933{
1934    executeTextEditCommand("Cut");
1935}
1936
1937void InputHandler::copy()
1938{
1939    executeTextEditCommand("Copy");
1940}
1941
1942void InputHandler::paste()
1943{
1944    executeTextEditCommand("Paste");
1945}
1946
1947void InputHandler::selectAll()
1948{
1949    executeTextEditCommand("SelectAll");
1950}
1951
1952void InputHandler::addAttributedTextMarker(int start, int end, const AttributeTextStyle& style)
1953{
1954    if ((end - start) < 1 || end > static_cast<int>(elementText().length()))
1955        return;
1956
1957    RefPtr<Range> markerRange = DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), start, end).toNormalizedRange();
1958    m_currentFocusElement->document()->markers()->addMarker(markerRange.get(), DocumentMarker::AttributeText, WTF::String("Input Marker"), style);
1959}
1960
1961void InputHandler::removeAttributedTextMarker()
1962{
1963    // Remove all attribute text markers.
1964    if (m_currentFocusElement && m_currentFocusElement->document())
1965        m_currentFocusElement->document()->markers()->removeMarkers(DocumentMarker::AttributeText);
1966
1967    m_composingTextStart = 0;
1968    m_composingTextEnd = 0;
1969}
1970
1971void InputHandler::clearCurrentFocusElement()
1972{
1973    if (m_currentFocusElement)
1974        m_currentFocusElement->blur();
1975}
1976
1977bool InputHandler::willOpenPopupForNode(Node* node)
1978{
1979    // This method must be kept synchronized with InputHandler::didNodeOpenPopup.
1980    if (!node)
1981        return false;
1982
1983    ASSERT(!node->isInShadowTree());
1984
1985    if (node->hasTagName(HTMLNames::selectTag) || node->hasTagName(HTMLNames::optionTag)) {
1986        // We open list popups for options and selects.
1987        return true;
1988    }
1989
1990    if (node->isElementNode()) {
1991        Element* element = toElement(node);
1992        if (DOMSupport::isPopupInputField(element))
1993            return true;
1994    }
1995
1996    return false;
1997}
1998
1999bool InputHandler::didNodeOpenPopup(Node* node)
2000{
2001    // This method must be kept synchronized with InputHandler::willOpenPopupForNode.
2002    if (!node)
2003        return false;
2004
2005    ASSERT(!node->isInShadowTree());
2006
2007    if (node->hasTagName(HTMLNames::selectTag))
2008        return openSelectPopup(static_cast<HTMLSelectElement*>(node));
2009
2010    if (node->hasTagName(HTMLNames::optionTag)) {
2011        HTMLOptionElement* optionElement = static_cast<HTMLOptionElement*>(node);
2012        return openSelectPopup(optionElement->ownerSelectElement());
2013    }
2014
2015    if (HTMLInputElement* element = node->toInputElement()) {
2016        if (DOMSupport::isDateTimeInputField(element))
2017            return openDatePopup(element, elementType(element));
2018
2019        if (DOMSupport::isColorInputField(element))
2020            return openColorPopup(element);
2021    }
2022    return false;
2023}
2024
2025bool InputHandler::openSelectPopup(HTMLSelectElement* select)
2026{
2027    if (!select || select->isDisabledFormControl())
2028        return false;
2029
2030    // If there's no view, do nothing and return.
2031    if (!select->document()->view())
2032        return false;
2033
2034    if (isActiveTextEdit())
2035        clearCurrentFocusElement();
2036
2037    m_currentFocusElement = select;
2038    m_currentFocusElementType = SelectPopup;
2039
2040    const WTF::Vector<HTMLElement*>& listItems = select->listItems();
2041    int size = listItems.size();
2042
2043    bool multiple = select->multiple();
2044    ScopeArray<BlackBerry::Platform::String> labels;
2045    labels.reset(new BlackBerry::Platform::String[size]);
2046
2047    bool* enableds = 0;
2048    int* itemTypes = 0;
2049    bool* selecteds = 0;
2050
2051    if (size) {
2052        enableds = new bool[size];
2053        itemTypes = new int[size];
2054        selecteds = new bool[size];
2055        for (int i = 0; i < size; i++) {
2056            if (listItems[i]->hasTagName(HTMLNames::optionTag)) {
2057                HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems[i]);
2058                labels[i] = option->textIndentedToRespectGroupLabel();
2059                enableds[i] = option->isDisabledFormControl() ? 0 : 1;
2060                selecteds[i] = option->selected();
2061                itemTypes[i] = option->parentNode() && option->parentNode()->hasTagName(HTMLNames::optgroupTag) ? TypeOptionInGroup : TypeOption;
2062            } else if (listItems[i]->hasTagName(HTMLNames::optgroupTag)) {
2063                HTMLOptGroupElement* optGroup = static_cast<HTMLOptGroupElement*>(listItems[i]);
2064                labels[i] = optGroup->groupLabelText();
2065                enableds[i] = optGroup->isDisabledFormControl() ? 0 : 1;
2066                selecteds[i] = false;
2067                itemTypes[i] = TypeGroup;
2068            } else if (listItems[i]->hasTagName(HTMLNames::hrTag)) {
2069                enableds[i] = false;
2070                selecteds[i] = false;
2071                itemTypes[i] = TypeSeparator;
2072            }
2073        }
2074    }
2075
2076    SelectPopupClient* selectClient = new SelectPopupClient(multiple, size, labels, enableds, itemTypes, selecteds, m_webPage, select);
2077    WebCore::IntRect elementRectInRootView = select->document()->view()->contentsToRootView(enclosingIntRect(select->getRect()));
2078    // Fail to create HTML popup, use the old path
2079    if (!m_webPage->openPagePopup(selectClient, elementRectInRootView))
2080        m_webPage->m_client->openPopupList(multiple, size, labels, enableds, itemTypes, selecteds);
2081    delete[] enableds;
2082    delete[] itemTypes;
2083    delete[] selecteds;
2084    return true;
2085}
2086
2087void InputHandler::setPopupListIndex(int index)
2088{
2089    if (index == -2) // Abandon
2090        return clearCurrentFocusElement();
2091
2092    if (!isActiveSelectPopup())
2093        return clearCurrentFocusElement();
2094
2095    RenderObject* renderer = m_currentFocusElement->renderer();
2096    if (renderer && renderer->isMenuList()) {
2097        RenderMenuList* renderMenu = toRenderMenuList(renderer);
2098        renderMenu->hidePopup();
2099    }
2100
2101    HTMLSelectElement* selectElement = static_cast<HTMLSelectElement*>(m_currentFocusElement.get());
2102    int optionIndex = selectElement->listToOptionIndex(index);
2103    selectElement->optionSelectedByUser(optionIndex, true /* deselect = true */, true /* fireOnChangeNow = false */);
2104    clearCurrentFocusElement();
2105}
2106
2107void InputHandler::setPopupListIndexes(int size, const bool* selecteds)
2108{
2109    if (!isActiveSelectPopup())
2110        return clearCurrentFocusElement();
2111
2112    if (size < 0)
2113        return;
2114
2115    HTMLSelectElement* selectElement = static_cast<HTMLSelectElement*>(m_currentFocusElement.get());
2116    const WTF::Vector<HTMLElement*>& items = selectElement->listItems();
2117    if (items.size() != static_cast<unsigned>(size))
2118        return;
2119
2120    HTMLOptionElement* option;
2121    for (int i = 0; i < size; i++) {
2122        if (items[i]->hasTagName(HTMLNames::optionTag)) {
2123            option = static_cast<HTMLOptionElement*>(items[i]);
2124            option->setSelectedState(selecteds[i]);
2125        }
2126    }
2127
2128    // Force repaint because we do not send mouse events to the select element
2129    // and the element doesn't automatically repaint itself.
2130    selectElement->dispatchFormControlChangeEvent();
2131    selectElement->renderer()->repaint();
2132    clearCurrentFocusElement();
2133}
2134
2135bool InputHandler::setBatchEditingActive(bool active)
2136{
2137    if (!isActiveTextEdit())
2138        return false;
2139
2140    ASSERT(m_currentFocusElement->document());
2141    ASSERT(m_currentFocusElement->document()->frame());
2142
2143    // FIXME switch this to m_currentFocusElement->document()->frame() when we have separate
2144    // backingstore for each frame.
2145    BackingStoreClient* backingStoreClient = m_webPage->backingStoreClient();
2146    ASSERT(backingStoreClient);
2147
2148    // Enable / Disable the backingstore to prevent visual updates.
2149    // FIXME: Do we really need to suspend/resume both backingstore and screen here?
2150    if (!active) {
2151        backingStoreClient->backingStore()->resumeBackingStoreUpdates();
2152        backingStoreClient->backingStore()->resumeScreenUpdates(BackingStore::RenderAndBlit);
2153    } else {
2154        backingStoreClient->backingStore()->suspendBackingStoreUpdates();
2155        backingStoreClient->backingStore()->suspendScreenUpdates();
2156    }
2157
2158    return true;
2159}
2160
2161bool InputHandler::isCaretAtEndOfText()
2162{
2163    return caretPosition() == static_cast<int>(elementText().length());
2164}
2165
2166int InputHandler::caretPosition() const
2167{
2168    if (!isActiveTextEdit())
2169        return -1;
2170
2171    // NOTE: This function is expected to return the start of the selection if
2172    // a selection is applied.
2173    return selectionStart();
2174}
2175
2176int relativeLeftOffset(int caretPosition, int leftOffset)
2177{
2178    ASSERT(caretPosition >= 0);
2179
2180    return std::max(0, caretPosition - leftOffset);
2181}
2182
2183int relativeRightOffset(int caretPosition, unsigned totalLengthOfText, int rightOffset)
2184{
2185    ASSERT(caretPosition >= 0);
2186    ASSERT(totalLengthOfText < INT_MAX);
2187
2188    return std::min(caretPosition + rightOffset, static_cast<int>(totalLengthOfText));
2189}
2190
2191bool InputHandler::deleteTextRelativeToCursor(int leftOffset, int rightOffset)
2192{
2193    if (!isActiveTextEdit() || compositionActive())
2194        return false;
2195
2196    InputLog(Platform::LogLevelInfo,
2197        "InputHandler::deleteTextRelativeToCursor left %d right %d",
2198        leftOffset, rightOffset);
2199
2200    int caretOffset = caretPosition();
2201    int start = relativeLeftOffset(caretOffset, leftOffset);
2202    int end = relativeRightOffset(caretOffset, elementText().length(), rightOffset);
2203
2204    // If we have backspace in a single character, send this to webkit as a KeyboardEvent. Otherwise, call deleteText.
2205    if (leftOffset == 1 && !rightOffset) {
2206        if (selectionActive())
2207            return deleteSelection();
2208
2209        if (!handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), true /* changeIsPartOfComposition */))
2210            return false;
2211    } else if (!deleteText(start, end))
2212        return false;
2213
2214    ProcessingChangeGuard guard(this);
2215
2216    // Scroll the field if necessary. The automatic update is suppressed
2217    // by the processing change guard.
2218    ensureFocusTextElementVisible(EdgeIfNeeded);
2219
2220    return true;
2221}
2222
2223bool InputHandler::deleteText(int start, int end)
2224{
2225    if (!isActiveTextEdit())
2226        return false;
2227
2228    {
2229        ProcessingChangeGuard guard(this);
2230
2231        if (end - start == 1)
2232            return handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), true /* changeIsPartOfComposition */);
2233
2234        if (!setSelection(start, end, true /*changeIsPartOfComposition*/))
2235            return false;
2236    }
2237
2238    InputLog(Platform::LogLevelInfo, "InputHandler::deleteText start %d end %d", start, end);
2239
2240    return deleteSelection();
2241}
2242
2243spannable_string_t* InputHandler::spannableTextInRange(int start, int end, int32_t)
2244{
2245    if (!isActiveTextEdit())
2246        return 0;
2247
2248    if (start >= end)
2249        return 0;
2250
2251    int length = end - start;
2252
2253    WTF::String textString = elementText().substring(start, length);
2254
2255    spannable_string_t* pst = (spannable_string_t*)fastMalloc(sizeof(spannable_string_t));
2256
2257    // Don't use fastMalloc in case the string is unreasonably long. fastMalloc will
2258    // crash immediately on failure.
2259    pst->str = (wchar_t*)malloc(sizeof(wchar_t) * (length + 1));
2260    if (!pst->str) {
2261        Platform::logAlways(Platform::LogLevelCritical, "InputHandler::spannableTextInRange Cannot allocate memory for string.");
2262        free(pst);
2263        return 0;
2264    }
2265
2266    int stringLength = 0;
2267    if (!convertStringToWchar(textString, pst->str, length + 1, &stringLength)) {
2268        Platform::logAlways(Platform::LogLevelCritical, "InputHandler::spannableTextInRange failed to convert string.");
2269        free(pst->str);
2270        free(pst);
2271        return 0;
2272    }
2273
2274    pst->length = stringLength;
2275    pst->spans_count = 0;
2276    pst->spans = 0;
2277
2278    return pst;
2279}
2280
2281spannable_string_t* InputHandler::selectedText(int32_t flags)
2282{
2283    if (!isActiveTextEdit())
2284        return 0;
2285
2286    return spannableTextInRange(selectionStart(), selectionEnd(), flags);
2287}
2288
2289spannable_string_t* InputHandler::textBeforeCursor(int32_t length, int32_t flags)
2290{
2291    if (!isActiveTextEdit())
2292        return 0;
2293
2294    int caretOffset = caretPosition();
2295    int start = relativeLeftOffset(caretOffset, length);
2296    int end = caretOffset;
2297
2298    return spannableTextInRange(start, end, flags);
2299}
2300
2301spannable_string_t* InputHandler::textAfterCursor(int32_t length, int32_t flags)
2302{
2303    if (!isActiveTextEdit())
2304        return 0;
2305
2306    int caretOffset = caretPosition();
2307    int start = caretOffset;
2308    int end = relativeRightOffset(caretOffset, elementText().length(), length);
2309
2310    return spannableTextInRange(start, end, flags);
2311}
2312
2313extracted_text_t* InputHandler::extractedTextRequest(extracted_text_request_t*, int32_t flags)
2314{
2315    if (!isActiveTextEdit())
2316        return 0;
2317
2318    extracted_text_t* extractedText = (extracted_text_t *)fastMalloc(sizeof(extracted_text_t));
2319
2320    // 'flags' indicates whether the text is being monitored. This is not currently used.
2321
2322    // FIXME add smart limiting based on the hint sizes. Currently return all text.
2323
2324    extractedText->text = spannableTextInRange(0, elementText().length(), flags);
2325
2326    // FIXME confirm these should be 0 on this requested. Text changes will likely require
2327    // the end be the length.
2328    extractedText->partial_start_offset = 0;
2329    extractedText->partial_end_offset = 0;
2330    extractedText->start_offset = 0;
2331
2332    // Adjust selection values relative to the start offset, which may be a subset
2333    // of the text in the field.
2334    extractedText->selection_start = selectionStart() - extractedText->start_offset;
2335    extractedText->selection_end = selectionEnd() - extractedText->start_offset;
2336
2337    // selectionActive is not limited to inside the extracted text.
2338    bool selectionActive = extractedText->selection_start != extractedText->selection_end;
2339    bool singleLine = m_currentFocusElement->hasTagName(HTMLNames::inputTag);
2340
2341    // FIXME flags has two values in doc, enum not in header yet.
2342    extractedText->flags = selectionActive & singleLine;
2343
2344    return extractedText;
2345}
2346
2347static void addCompositionTextStyleToAttributeTextStyle(AttributeTextStyle& style)
2348{
2349    style.setUnderline(AttributeTextStyle::StandardUnderline);
2350}
2351
2352static void addActiveTextStyleToAttributeTextStyle(AttributeTextStyle& style)
2353{
2354    style.setBackgroundColor(Color("blue"));
2355    style.setTextColor(Color("white"));
2356}
2357
2358static AttributeTextStyle compositionTextStyle()
2359{
2360    AttributeTextStyle style;
2361    addCompositionTextStyleToAttributeTextStyle(style);
2362    return style;
2363}
2364
2365static AttributeTextStyle textStyleFromMask(int64_t mask)
2366{
2367    AttributeTextStyle style;
2368    if (mask & COMPOSED_TEXT_ATTRIB)
2369        addCompositionTextStyleToAttributeTextStyle(style);
2370
2371    if (mask & ACTIVE_REGION_ATTRIB)
2372        addActiveTextStyleToAttributeTextStyle(style);
2373
2374    return style;
2375}
2376
2377bool InputHandler::removeComposedText()
2378{
2379    if (compositionActive()) {
2380        if (!deleteText(m_composingTextStart, m_composingTextEnd)) {
2381            // Could not remove the existing composition region.
2382            return false;
2383        }
2384        removeAttributedTextMarker();
2385    }
2386
2387    return true;
2388}
2389
2390int32_t InputHandler::setComposingRegion(int32_t start, int32_t end)
2391{
2392    if (!isActiveTextEdit())
2393        return -1;
2394
2395    if (!removeComposedText()) {
2396        // Could not remove the existing composition region.
2397        return -1;
2398    }
2399
2400    m_composingTextStart = start;
2401    m_composingTextEnd = end;
2402
2403    if (compositionActive())
2404        addAttributedTextMarker(start, end, compositionTextStyle());
2405
2406    InputLog(Platform::LogLevelInfo, "InputHandler::setComposingRegion start %d end %d", start, end);
2407
2408    return 0;
2409}
2410
2411int32_t InputHandler::finishComposition()
2412{
2413    if (!isActiveTextEdit())
2414        return -1;
2415
2416    // FIXME if no composition is active, should we return failure -1?
2417    if (!compositionActive())
2418        return 0;
2419
2420    // Remove all markers.
2421    removeAttributedTextMarker();
2422
2423    InputLog(Platform::LogLevelInfo, "InputHandler::finishComposition completed");
2424
2425    return 0;
2426}
2427
2428span_t* InputHandler::firstSpanInString(spannable_string_t* spannableString, SpannableStringAttribute attrib)
2429{
2430    span_t* span = spannableString->spans;
2431    for (unsigned i = 0; i < spannableString->spans_count; i++) {
2432        if (span->attributes_mask & attrib)
2433            return span;
2434        span++;
2435    }
2436
2437    return 0;
2438}
2439
2440bool InputHandler::isTrailingSingleCharacter(span_t* span, unsigned stringLength, unsigned composingTextLength)
2441{
2442    // Make sure the new string is one character larger than the old.
2443    if (composingTextLength != stringLength - 1)
2444        return false;
2445
2446    if (!span)
2447        return false;
2448
2449    // Has only 1 character changed?
2450    if (span->start == span->end) {
2451        // Is this character the last character in the string?
2452        if (span->start == stringLength - 1)
2453            return true;
2454    }
2455    // Return after the first changed_attrib is found.
2456    return false;
2457}
2458
2459bool InputHandler::setText(spannable_string_t* spannableString)
2460{
2461    if (!isActiveTextEdit() || !spannableString)
2462        return false;
2463
2464    ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
2465    Frame* frame = m_currentFocusElement->document()->frame();
2466
2467    Editor* editor = frame->editor();
2468    ASSERT(editor);
2469
2470    // Disable selectionHandler's active selection as we will be resetting and these
2471    // changes should not be handled as notification event.
2472    m_webPage->m_selectionHandler->setSelectionActive(false);
2473
2474    WTF::String textToInsert = convertSpannableStringToString(spannableString);
2475    int textLength = textToInsert.length();
2476
2477    InputLog(Platform::LogLevelInfo,
2478        "InputHandler::setText spannableString is '%s', of length %d",
2479        textToInsert.latin1().data(), textLength);
2480
2481    span_t* changedSpan = firstSpanInString(spannableString, CHANGED_ATTRIB);
2482    int composingTextStart = m_composingTextStart;
2483    int composingTextEnd = m_composingTextEnd;
2484    int composingTextLength = compositionLength();
2485    removeAttributedTextMarker();
2486
2487    if (isTrailingSingleCharacter(changedSpan, textLength, composingTextLength)) {
2488        // If the text is unconverted, do not allow JS processing as it is not a "real"
2489        // character in the field.
2490        if (firstSpanInString(spannableString, UNCONVERTED_TEXT_ATTRIB)) {
2491            InputLog(Platform::LogLevelInfo, "InputHandler::setText Single trailing character detected. Text is unconverted.");
2492            return editor->command("InsertText").execute(textToInsert.right(1));
2493        }
2494        InputLog(Platform::LogLevelInfo, "InputHandler::setText Single trailing character detected.");
2495        return handleKeyboardInput(Platform::KeyboardEvent(textToInsert[textLength - 1], Platform::KeyboardEvent::KeyDown, 0), false /* changeIsPartOfComposition */);
2496    }
2497
2498    // If no spans have changed, treat it as a delete operation.
2499    if (!changedSpan) {
2500        // If the composition length is the same as our string length, then we don't need to do anything.
2501        if (composingTextLength == textLength) {
2502            InputLog(Platform::LogLevelInfo, "InputHandler::setText No spans have changed. New text is the same length as the old. Nothing to do.");
2503            return true;
2504        }
2505
2506        if (composingTextLength - textLength == 1) {
2507            InputLog(Platform::LogLevelInfo, "InputHandler::setText No spans have changed. New text is one character shorter than the old. Treating as 'delete'.");
2508            return handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), true /* changeIsPartOfComposition */);
2509        }
2510    }
2511
2512    if (composingTextLength && !setSelection(composingTextStart, composingTextEnd, true /* changeIsPartOfComposition */))
2513        return false;
2514
2515    // If there is no text to add just delete.
2516    if (!textLength) {
2517        if (selectionActive())
2518            return handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), true /* changeIsPartOfComposition */);
2519
2520        // Nothing to do.
2521        return true;
2522    }
2523
2524    // Triggering an insert of the text with a space character trailing
2525    // causes new text to adopt the previous text style.
2526    // Remove it and apply it as a keypress later.
2527    // Upstream Webkit bug created https://bugs.webkit.org/show_bug.cgi?id=70823
2528    bool requiresSpaceKeyPress = false;
2529    if (textLength > 0 && textToInsert[textLength - 1] == KEYCODE_SPACE) {
2530        requiresSpaceKeyPress = true;
2531        textLength--;
2532        textToInsert.remove(textLength, 1);
2533    }
2534
2535    InputLog(Platform::LogLevelInfo,
2536        "InputHandler::setText Request being processed. Text before processing: '%s'",
2537        elementText().latin1().data());
2538
2539    if (textLength == 1 && !spannableString->spans_count) {
2540        // Handle single key non-attributed entry as key press rather than insert to allow
2541        // triggering of javascript events.
2542        InputLog(Platform::LogLevelInfo, "InputHandler::setText Single character entry treated as key-press in the absense of spans.");
2543        return handleKeyboardInput(Platform::KeyboardEvent(textToInsert[0], Platform::KeyboardEvent::KeyDown, 0), true /* changeIsPartOfComposition */);
2544    }
2545
2546    // Perform the text change as a single command if there is one.
2547    if (!textToInsert.isEmpty() && !editor->command("InsertText").execute(textToInsert)) {
2548        InputLog(Platform::LogLevelWarn,
2549            "InputHandler::setText Failed to insert text '%s'",
2550            textToInsert.latin1().data());
2551        return false;
2552    }
2553
2554    if (requiresSpaceKeyPress)
2555        handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_SPACE, Platform::KeyboardEvent::KeyDown, 0), true /* changeIsPartOfComposition */);
2556
2557    InputLog(Platform::LogLevelInfo,
2558        "InputHandler::setText Request being processed. Text after processing '%s'",
2559        elementText().latin1().data());
2560
2561    return true;
2562}
2563
2564bool InputHandler::setTextAttributes(int insertionPoint, spannable_string_t* spannableString)
2565{
2566    // Apply the attributes to the field.
2567    span_t* span = spannableString->spans;
2568    for (unsigned i = 0; i < spannableString->spans_count; i++) {
2569        unsigned startPosition = insertionPoint + span->start;
2570        // The end point includes the character that it is before. Ie, 0, 0
2571        // applies to the first character as the end point includes the character
2572        // at the position. This means the endPosition is always +1.
2573        unsigned endPosition = insertionPoint + span->end + 1;
2574        if (endPosition < startPosition || endPosition > elementText().length())
2575            return false;
2576
2577        if (!span->attributes_mask)
2578            continue; // Nothing to do.
2579
2580        // MISSPELLED_WORD_ATTRIB is present as an option, but it is not currently
2581        // used by IMF. When they add support for on the fly spell checking we can
2582        // use it to apply spelling markers and disable continuous spell checking.
2583
2584        InputLog(Platform::LogLevelInfo,
2585            "InputHandler::setTextAttributes adding marker %d to %d - %llu",
2586            startPosition, endPosition, span->attributes_mask);
2587        addAttributedTextMarker(startPosition, endPosition, textStyleFromMask(span->attributes_mask));
2588
2589        span++;
2590    }
2591
2592    InputLog(Platform::LogLevelInfo, "InputHandler::setTextAttributes attribute count %d", spannableString->spans_count);
2593
2594    return true;
2595}
2596
2597bool InputHandler::setRelativeCursorPosition(int insertionPoint, int relativeCursorPosition)
2598{
2599    if (!isActiveTextEdit())
2600        return false;
2601
2602    // 1 place cursor at end of insertion text.
2603    if (relativeCursorPosition == 1) {
2604        m_currentFocusElement->document()->frame()->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
2605        return true;
2606    }
2607
2608    int cursorPosition = 0;
2609    if (relativeCursorPosition <= 0) {
2610        // Zero = insertionPoint
2611        // Negative value, move the cursor the requested number of characters before
2612        // the start of the inserted text.
2613        cursorPosition = insertionPoint + relativeCursorPosition;
2614    } else {
2615        // Positive value, move the cursor the requested number of characters after
2616        // the end of the inserted text minus 1.
2617        cursorPosition = caretPosition() + relativeCursorPosition - 1;
2618    }
2619
2620    if (cursorPosition < 0 || cursorPosition > (int)elementText().length())
2621        return false;
2622
2623    InputLog(Platform::LogLevelInfo,
2624        "InputHandler::setRelativeCursorPosition cursor position %d",
2625        cursorPosition);
2626
2627    return setCursorPosition(cursorPosition);
2628}
2629
2630bool InputHandler::setSpannableTextAndRelativeCursor(spannable_string_t* spannableString, int relativeCursorPosition, bool markTextAsComposing)
2631{
2632    InputLog(Platform::LogLevelInfo,
2633        "InputHandler::setSpannableTextAndRelativeCursor(%d, %d, %d)",
2634        spannableString->length, relativeCursorPosition, markTextAsComposing);
2635
2636    int insertionPoint = compositionActive() ? m_composingTextStart : selectionStart();
2637
2638    ProcessingChangeGuard guard(this);
2639
2640    if (!setText(spannableString))
2641        return false;
2642
2643    if (!setTextAttributes(insertionPoint, spannableString))
2644        return false;
2645
2646    if (!setRelativeCursorPosition(insertionPoint, relativeCursorPosition))
2647        return false;
2648
2649    if (markTextAsComposing) {
2650        m_composingTextStart = insertionPoint;
2651        m_composingTextEnd = insertionPoint + spannableString->length;
2652    }
2653
2654    return true;
2655}
2656
2657int32_t InputHandler::setComposingText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
2658{
2659    if (!isActiveTextEdit())
2660        return -1;
2661
2662    if (!spannableString)
2663        return -1;
2664
2665    InputLog(Platform::LogLevelInfo,
2666        "InputHandler::setComposingText at relativeCursorPosition: %d",
2667        relativeCursorPosition);
2668
2669    // Enable input mode if we are processing a key event.
2670    setInputModeEnabled();
2671
2672    return setSpannableTextAndRelativeCursor(spannableString, relativeCursorPosition, true /* markTextAsComposing */) ? 0 : -1;
2673}
2674
2675int32_t InputHandler::commitText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
2676{
2677    if (!isActiveTextEdit())
2678        return -1;
2679
2680    if (!spannableString)
2681        return -1;
2682
2683    InputLog(Platform::LogLevelInfo, "InputHandler::commitText");
2684
2685    return setSpannableTextAndRelativeCursor(spannableString, relativeCursorPosition, false /* markTextAsComposing */) ? 0 : -1;
2686}
2687
2688void InputHandler::restoreViewState()
2689{
2690    setInputModeEnabled();
2691
2692    // Make sure we reset the selection / FCC state.
2693    m_webPage->m_selectionHandler->selectionPositionChanged();
2694}
2695
2696void InputHandler::showTextInputTypeSuggestionBox(bool allowEmptyPrefix)
2697{
2698    if (!isActiveTextEdit())
2699        return;
2700
2701    HTMLInputElement* focusedInputElement = static_cast<HTMLInputElement*>(m_currentFocusElement->toInputElement());
2702    if (!focusedInputElement)
2703        return;
2704
2705    if (!m_suggestionDropdownBoxHandler)
2706        m_suggestionDropdownBoxHandler = SuggestionBoxHandler::create(focusedInputElement);
2707
2708    // If the focused input element isn't the same as the one inside the handler, reset and display.
2709    // If the focused element is the same, display the suggestions.
2710    if ((m_suggestionDropdownBoxHandler->focusedElement()) && m_suggestionDropdownBoxHandler->focusedElement() != focusedInputElement)
2711        m_suggestionDropdownBoxHandler->setInputElementAndUpdateDisplay(focusedInputElement);
2712    else
2713        m_suggestionDropdownBoxHandler->showDropdownBox(allowEmptyPrefix);
2714}
2715
2716void InputHandler::hideTextInputTypeSuggestionBox()
2717{
2718    if (m_suggestionDropdownBoxHandler)
2719        m_suggestionDropdownBoxHandler->hideDropdownBox();
2720}
2721
2722void InputHandler::elementTouched(WebCore::Element* nonShadowElementUnderFatFinger)
2723{
2724    // Attempt to show all suggestions when the input field is empty and a tap is registered when the element is focused.
2725    if (isActiveTextEdit() && nonShadowElementUnderFatFinger == m_currentFocusElement)
2726        showTextInputTypeSuggestionBox(true /* allowEmptyPrefix */);
2727
2728    m_elementTouchedIsCrossFrame = nonShadowElementUnderFatFinger
2729        && nonShadowElementUnderFatFinger->document()
2730        && nonShadowElementUnderFatFinger->document()->frame() != m_webPage->focusedOrMainFrame();
2731}
2732
2733}
2734}
2735