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, ¶graphLength)) { 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