#include "config.h"
#include "InputHandler.h"
#include "BackingStore.h"
#include "BackingStoreClient.h"
#include "CSSStyleDeclaration.h"
#include "Chrome.h"
#include "ColorPickerClient.h"
#include "DOMSupport.h"
#include "DatePickerClient.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "DocumentMarkerController.h"
#include "EditorClientBlackBerry.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameView.h"
#include "HTMLFormElement.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLOptGroupElement.h"
#include "HTMLOptionElement.h"
#include "HTMLSelectElement.h"
#include "HTMLTextAreaElement.h"
#include "NotImplemented.h"
#include "Page.h"
#include "PlatformKeyboardEvent.h"
#include "PluginView.h"
#include "Range.h"
#include "RenderLayer.h"
#include "RenderMenuList.h"
#include "RenderPart.h"
#include "RenderText.h"
#include "RenderTextControl.h"
#include "RenderWidget.h"
#include "RenderedDocumentMarker.h"
#include "ScopePointer.h"
#include "SelectPopupClient.h"
#include "SelectionHandler.h"
#include "SpellChecker.h"
#include "SpellingHandler.h"
#include "SuggestionBoxHandler.h"
#include "TextCheckerClient.h"
#include "TextIterator.h"
#include "VisiblePosition.h"
#include "VisibleUnits.h"
#include "WebKitThreadViewportAccessor.h"
#include "WebPageClient.h"
#include "WebPage_p.h"
#include "WebSettings.h"
#include "htmlediting.h"
#include <BlackBerryPlatformDeviceInfo.h>
#include <BlackBerryPlatformIMF.h>
#include <BlackBerryPlatformKeyboardEvent.h>
#include <BlackBerryPlatformLog.h>
#include <BlackBerryPlatformScreen.h>
#include <BlackBerryPlatformSettings.h>
#include <cmath>
#include <sys/keycodes.h>
#include <wtf/text/CString.h>
#define ENABLE_INPUT_LOG 0
#define ENABLE_FOCUS_LOG 0
#define ENABLE_SPELLING_LOG 0
using namespace BlackBerry::Platform;
using namespace WebCore;
#if ENABLE_INPUT_LOG
#define InputLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
#else
#define InputLog(severity, format, ...)
#endif // ENABLE_INPUT_LOG
#if ENABLE_FOCUS_LOG
#define FocusLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
#else
#define FocusLog(severity, format, ...)
#endif // ENABLE_FOCUS_LOG
#if ENABLE_SPELLING_LOG
#define SpellingLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
#else
#define SpellingLog(severity, format, ...)
#endif // ENABLE_SPELLING_LOG
namespace BlackBerry {
namespace WebKit {
static const float zoomAnimationThreshold = 0.5;
class ProcessingChangeGuard {
public:
ProcessingChangeGuard(InputHandler* inputHandler)
: m_inputHandler(inputHandler)
, m_savedProcessingChange(false)
{
ASSERT(m_inputHandler);
m_savedProcessingChange = m_inputHandler->processingChange();
m_inputHandler->setProcessingChange(true);
}
~ProcessingChangeGuard()
{
m_inputHandler->setProcessingChange(m_savedProcessingChange);
}
private:
InputHandler* m_inputHandler;
bool m_savedProcessingChange;
};
InputHandler::InputHandler(WebPagePrivate* page)
: m_webPage(page)
, m_currentFocusElement(0)
, m_previousFocusableTextElement(0)
, m_nextFocusableTextElement(0)
, m_hasSubmitButton(false)
, m_inputModeEnabled(false)
, m_processingChange(false)
, m_shouldEnsureFocusTextElementVisibleOnSelectionChanged(false)
, m_currentFocusElementType(TextEdit)
, m_currentFocusElementTextEditMask(DEFAULT_STYLE)
, m_composingTextStart(0)
, m_composingTextEnd(0)
, m_pendingKeyboardVisibilityChange(NoChange)
, m_delayKeyboardVisibilityChange(false)
, m_sendFormStateOnNextKeyboardRequest(false)
, m_request(0)
, m_processingTransactionId(-1)
, m_shouldNotifyWebView(true)
, m_expectedKeyUpChar(0)
, m_didSpellCheckWord(false)
, m_spellingHandler(new SpellingHandler(this))
, m_spellCheckStatusConfirmed(false)
, m_globalSpellCheckStatus(false)
, m_minimumSpellCheckingRequestSequence(-1)
, m_elementTouchedIsCrossFrame(false)
{
}
InputHandler::~InputHandler()
{
delete m_spellingHandler;
}
static BlackBerryInputType convertInputType(const HTMLInputElement* inputElement)
{
if (inputElement->isPasswordField())
return InputTypePassword;
if (inputElement->isSearchField())
return InputTypeSearch;
if (inputElement->isEmailField())
return InputTypeEmail;
if (inputElement->isMonthField())
return InputTypeMonth;
if (inputElement->isNumberField())
return InputTypeNumber;
if (inputElement->isTelephoneField())
return InputTypeTelephone;
if (inputElement->isURLField())
return InputTypeURL;
#if ENABLE(INPUT_TYPE_COLOR)
if (inputElement->isColorControl())
return InputTypeColor;
#endif
if (inputElement->isDateField())
return InputTypeDate;
if (inputElement->isDateTimeField())
return InputTypeDateTime;
if (inputElement->isDateTimeLocalField())
return InputTypeDateTimeLocal;
if (inputElement->isTimeField())
return InputTypeTime;
if (DOMSupport::elementIdOrNameIndicatesEmail(inputElement))
return InputTypeEmail;
if (DOMSupport::elementIdOrNameIndicatesUrl(inputElement))
return InputTypeURL;
if (DOMSupport::elementPatternIndicatesNumber(inputElement))
return InputTypeNumber;
if (DOMSupport::elementPatternIndicatesHexadecimal(inputElement))
return InputTypeHexadecimal;
return InputTypeText;
}
static int64_t inputStyle(BlackBerryInputType type, const Element* element)
{
switch (type) {
case InputTypeEmail:
case InputTypeURL:
case InputTypeSearch:
case InputTypeText:
case InputTypeTextArea:
{
DOMSupport::AttributeState autoCompleteState = DOMSupport::elementSupportsAutocomplete(element);
DOMSupport::AttributeState autoCorrectState = DOMSupport::elementSupportsAutocorrect(element);
if (autoCompleteState == DOMSupport::Off) {
if (autoCorrectState == DOMSupport::On)
return NO_PREDICTION;
return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
}
if (autoCompleteState == DOMSupport::On) {
if (autoCorrectState == DOMSupport::Off)
return NO_AUTO_TEXT | NO_AUTO_CORRECTION;
return DEFAULT_STYLE;
}
if (type == InputTypeEmail || type == InputTypeURL || DOMSupport::elementIdOrNameIndicatesNoAutocomplete(element)) {
if (autoCorrectState == DOMSupport::On)
return NO_PREDICTION;
return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
}
if (autoCorrectState == DOMSupport::On || (type == InputTypeTextArea && autoCorrectState != DOMSupport::Off))
return DEFAULT_STYLE;
return NO_AUTO_TEXT | NO_AUTO_CORRECTION;
}
case InputTypePassword:
case InputTypeNumber:
case InputTypeTelephone:
case InputTypeHexadecimal:
return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
default:
break;
}
return DEFAULT_STYLE;
}
static VirtualKeyboardType convertInputTypeToVKBType(BlackBerryInputType inputType)
{
switch (inputType) {
case InputTypeURL:
return VKBTypeUrl;
case InputTypeEmail:
return VKBTypeEmail;
case InputTypeTelephone:
return VKBTypePhone;
case InputTypePassword:
return VKBTypePassword;
case InputTypeNumber:
case InputTypeHexadecimal:
return VKBTypePin;
default:
return VKBTypeDefault;
}
}
static VirtualKeyboardType convertStringToKeyboardType(const AtomicString& string)
{
DEFINE_STATIC_LOCAL(AtomicString, Default, ("default"));
DEFINE_STATIC_LOCAL(AtomicString, Url, ("url"));
DEFINE_STATIC_LOCAL(AtomicString, Email, ("email"));
DEFINE_STATIC_LOCAL(AtomicString, Password, ("password"));
DEFINE_STATIC_LOCAL(AtomicString, Web, ("web"));
DEFINE_STATIC_LOCAL(AtomicString, Number, ("number"));
DEFINE_STATIC_LOCAL(AtomicString, Symbol, ("symbol"));
DEFINE_STATIC_LOCAL(AtomicString, Phone, ("phone"));
DEFINE_STATIC_LOCAL(AtomicString, Pin, ("pin"));
DEFINE_STATIC_LOCAL(AtomicString, Hex, ("hexadecimal"));
if (string.isEmpty())
return VKBTypeNotSet;
if (equalIgnoringCase(string, Default))
return VKBTypeDefault;
if (equalIgnoringCase(string, Url))
return VKBTypeUrl;
if (equalIgnoringCase(string, Email))
return VKBTypeEmail;
if (equalIgnoringCase(string, Password))
return VKBTypePassword;
if (equalIgnoringCase(string, Web))
return VKBTypeWeb;
if (equalIgnoringCase(string, Number))
return VKBTypeNumPunc;
if (equalIgnoringCase(string, Symbol))
return VKBTypeSymbol;
if (equalIgnoringCase(string, Phone))
return VKBTypePhone;
if (equalIgnoringCase(string, Pin) || equalIgnoringCase(string, Hex))
return VKBTypePin;
return VKBTypeNotSet;
}
static VirtualKeyboardType keyboardTypeAttribute(const WebCore::Element* element)
{
DEFINE_STATIC_LOCAL(QualifiedName, keyboardTypeAttr, (nullAtom, "data-blackberry-virtual-keyboard-type", nullAtom));
if (element->fastHasAttribute(keyboardTypeAttr)) {
AtomicString attributeString = element->fastGetAttribute(keyboardTypeAttr);
return convertStringToKeyboardType(attributeString);
}
if (element->isFormControlElement()) {
const HTMLFormControlElement* formElement = static_cast<const HTMLFormControlElement*>(element);
if (formElement->form() && formElement->form()->fastHasAttribute(keyboardTypeAttr)) {
AtomicString attributeString = formElement->form()->fastGetAttribute(keyboardTypeAttr);
return convertStringToKeyboardType(attributeString);
}
}
return VKBTypeNotSet;
}
static VirtualKeyboardEnterKeyType convertStringToKeyboardEnterKeyType(const AtomicString& string)
{
DEFINE_STATIC_LOCAL(AtomicString, Default, ("default"));
DEFINE_STATIC_LOCAL(AtomicString, Connect, ("connect"));
DEFINE_STATIC_LOCAL(AtomicString, Done, ("done"));
DEFINE_STATIC_LOCAL(AtomicString, Go, ("go"));
DEFINE_STATIC_LOCAL(AtomicString, Join, ("join"));
DEFINE_STATIC_LOCAL(AtomicString, Next, ("next"));
DEFINE_STATIC_LOCAL(AtomicString, Search, ("search"));
DEFINE_STATIC_LOCAL(AtomicString, Send, ("send"));
DEFINE_STATIC_LOCAL(AtomicString, Submit, ("submit"));
if (string.isEmpty())
return VKBEnterKeyNotSet;
if (equalIgnoringCase(string, Default))
return VKBEnterKeyDefault;
if (equalIgnoringCase(string, Connect))
return VKBEnterKeyConnect;
if (equalIgnoringCase(string, Done))
return VKBEnterKeyDone;
if (equalIgnoringCase(string, Go))
return VKBEnterKeyGo;
if (equalIgnoringCase(string, Join))
return VKBEnterKeyJoin;
if (equalIgnoringCase(string, Next))
return VKBEnterKeyNext;
if (equalIgnoringCase(string, Search))
return VKBEnterKeySearch;
if (equalIgnoringCase(string, Send))
return VKBEnterKeySend;
if (equalIgnoringCase(string, Submit))
return VKBEnterKeySubmit;
return VKBEnterKeyNotSet;
}
static VirtualKeyboardEnterKeyType keyboardEnterKeyTypeAttribute(const WebCore::Element* element)
{
DEFINE_STATIC_LOCAL(QualifiedName, keyboardEnterKeyTypeAttr, (nullAtom, "data-blackberry-virtual-keyboard-enter-key", nullAtom));
if (element->fastHasAttribute(keyboardEnterKeyTypeAttr)) {
AtomicString attributeString = element->fastGetAttribute(keyboardEnterKeyTypeAttr);
return convertStringToKeyboardEnterKeyType(attributeString);
}
if (element->isFormControlElement()) {
const HTMLFormControlElement* formElement = static_cast<const HTMLFormControlElement*>(element);
if (formElement->form() && formElement->form()->fastHasAttribute(keyboardEnterKeyTypeAttr)) {
AtomicString attributeString = formElement->form()->fastGetAttribute(keyboardEnterKeyTypeAttr);
return convertStringToKeyboardEnterKeyType(attributeString);
}
}
return VKBEnterKeyNotSet;
}
void InputHandler::setProcessingChange(bool processingChange)
{
if (processingChange == m_processingChange)
return;
m_processingChange = processingChange;
if (!m_processingChange)
m_webPage->m_selectionHandler->inputHandlerDidFinishProcessingChange();
}
WTF::String InputHandler::elementText()
{
if (!isActiveTextEdit())
return WTF::String();
return DOMSupport::inputElementText(m_currentFocusElement.get());
}
BlackBerryInputType InputHandler::elementType(Element* element) const
{
if (const HTMLInputElement* inputElement = static_cast<const HTMLInputElement*>(element->toInputElement()))
return convertInputType(inputElement);
if (element->hasTagName(HTMLNames::textareaTag))
return InputTypeTextArea;
return InputTypeTextArea;
}
void InputHandler::focusedNodeChanged()
{
ASSERT(m_webPage->m_page->focusController());
Frame* frame = m_webPage->m_page->focusController()->focusedOrMainFrame();
if (!frame || !frame->document())
return;
Node* node = frame->document()->focusedElement();
if (isActiveTextEdit() && m_currentFocusElement == node) {
notifyClientOfKeyboardVisibilityChange(true);
return;
}
if (node && node->isElementNode()) {
Element* element = toElement(node);
if (DOMSupport::isElementTypePlugin(element)) {
setPluginFocused(element);
return;
}
if (DOMSupport::isTextBasedContentEditableElement(element) && !DOMSupport::isElementReadOnly(element)) {
setElementFocused(element);
return;
}
} else if (node && DOMSupport::isTextBasedContentEditableElement(node->parentElement()) && !DOMSupport::isElementReadOnly(node->parentElement())) {
setElementFocused(node->parentElement());
return;
}
if (isActiveTextEdit() && m_currentFocusElement->isContentEditable()) {
if (processingChange())
return;
Frame* frame = m_currentFocusElement->document()->frame();
ASSERT(frame);
if (frame == m_webPage->focusedOrMainFrame()
&& frame->selection()->start().anchorNode()
&& frame->selection()->start().anchorNode()->isContentEditable()
&& !m_elementTouchedIsCrossFrame)
return;
}
setElementUnfocused();
}
void InputHandler::setPluginFocused(Element* element)
{
ASSERT(DOMSupport::isElementTypePlugin(element));
if (isActiveTextEdit())
setElementUnfocused();
m_currentFocusElementType = Plugin;
m_currentFocusElement = element;
}
static bool convertStringToWchar(const WTF::String& string, wchar_t* dest, int destCapacity, int* destLength)
{
ASSERT(dest);
int length = string.length();
if (!length) {
destLength = 0;
return true;
}
UErrorCode ec = U_ZERO_ERROR;
u_strToUTF32(reinterpret_cast<UChar32*>(dest), destCapacity, destLength, string.characters(), length, &ec);
if (ec) {
Platform::logAlways(Platform::LogLevelCritical, "InputHandler::convertStringToWchar Error converting string ec (%d).", ec);
destLength = 0;
return false;
}
return true;
}
static bool convertStringToWcharVector(const WTF::String& string, WTF::Vector<wchar_t>& wcharString)
{
ASSERT(wcharString.isEmpty());
int length = string.length();
if (!length)
return true;
if (!wcharString.tryReserveCapacity(length + 1)) {
Platform::logAlways(Platform::LogLevelCritical, "InputHandler::convertStringToWcharVector Cannot allocate memory for string.");
return false;
}
int destLength = 0;
if (!convertStringToWchar(string, wcharString.data(), length + 1, &destLength))
return false;
wcharString.resize(destLength);
return true;
}
static WTF::String convertSpannableStringToString(spannable_string_t* src)
{
if (!src || !src->str || !src->length)
return WTF::String();
WTF::Vector<UChar> dest;
int destCapacity = (src->length * 2) + 1;
if (!dest.tryReserveCapacity(destCapacity)) {
Platform::logAlways(Platform::LogLevelCritical, "InputHandler::convertSpannableStringToString Cannot allocate memory for string.");
return WTF::String();
}
int destLength = 0;
UErrorCode ec = U_ZERO_ERROR;
u_strFromUTF32(dest.data(), destCapacity, &destLength, reinterpret_cast<UChar32*>(src->str), src->length, &ec);
if (ec) {
Platform::logAlways(Platform::LogLevelCritical, "InputHandler::convertSpannableStringToString Error converting string ec (%d).", ec);
return WTF::String();
}
dest.resize(destLength);
return WTF::String(dest.data(), destLength);
}
void InputHandler::sendLearnTextDetails(const WTF::String& string)
{
Vector<wchar_t> wcharString;
if (!convertStringToWcharVector(string, wcharString) || wcharString.isEmpty())
return;
m_webPage->m_client->inputLearnText(wcharString.data(), wcharString.size());
}
void InputHandler::learnText()
{
if (!isActiveTextEdit())
return;
if (m_currentFocusElementTextEditMask & NO_PREDICTION || m_currentFocusElementTextEditMask & NO_AUTO_TEXT)
return;
WTF::String textInField(elementText());
if (textInField.length() >= MaxLearnTextDataSize)
textInField = textInField.substring(std::max(0, static_cast<int>(caretPosition() - MaxLearnTextDataSize)), std::min(textInField.length(), MaxLearnTextDataSize));
textInField = textInField.stripWhiteSpace();
ASSERT(textInField.length() <= MaxLearnTextDataSize);
if (textInField.isEmpty())
return;
InputLog(Platform::LogLevelInfo, "InputHandler::learnText '%s'", textInField.latin1().data());
sendLearnTextDetails(textInField);
}
void InputHandler::callRequestCheckingFor(PassRefPtr<WebCore::SpellCheckRequest> spellCheckRequest)
{
if (SpellChecker* spellChecker = getSpellChecker())
spellChecker->requestCheckingFor(spellCheckRequest);
}
void InputHandler::requestCheckingOfString(PassRefPtr<WebCore::SpellCheckRequest> spellCheckRequest)
{
SpellingLog(Platform::LogLevelInfo, "InputHandler::requestCheckingOfString '%s'", spellCheckRequest->data().text().latin1().data());
if (!spellCheckRequest) {
SpellingLog(Platform::LogLevelWarn, "InputHandler::requestCheckingOfString did not receive a valid request.");
return;
}
if (spellCheckRequest->data().sequence() <= m_minimumSpellCheckingRequestSequence) {
SpellingLog(Platform::LogLevelWarn, "InputHandler::requestCheckingOfString rejecting stale request with sequenceId=%d. Sentinal currently at %d."
, spellCheckRequest->data().sequence(), m_minimumSpellCheckingRequestSequence);
spellCheckRequest->didCancel();
return;
}
unsigned requestLength = spellCheckRequest->data().text().length();
if (!isActiveTextEdit() || !shouldSpellCheckElement(m_currentFocusElement.get()) || requestLength < 2) {
SpellingLog(Platform::LogLevelWarn, "InputHandler::requestCheckingOfString request cancelled");
spellCheckRequest->didCancel();
return;
}
if (requestLength >= MaxSpellCheckingStringLength) {
spellCheckRequest->didCancel();
m_spellingHandler->spellCheckTextBlock(m_currentFocusElement.get(), TextCheckingProcessIncremental);
return;
}
wchar_t* checkingString = (wchar_t*)malloc(sizeof(wchar_t) * (requestLength + 1));
if (!checkingString) {
Platform::logAlways(Platform::LogLevelCritical, "InputHandler::requestCheckingOfString Cannot allocate memory for string.");
spellCheckRequest->didCancel();
return;
}
int paragraphLength = 0;
if (!convertStringToWchar(spellCheckRequest->data().text(), checkingString, requestLength + 1, ¶graphLength)) {
Platform::logAlways(Platform::LogLevelCritical, "InputHandler::requestCheckingOfString Failed to convert String to wchar type.");
free(checkingString);
spellCheckRequest->didCancel();
return;
}
m_processingTransactionId = m_webPage->m_client->checkSpellingOfStringAsync(checkingString, static_cast<unsigned>(paragraphLength));
free(checkingString);
if (m_processingTransactionId == -1) { spellCheckRequest->didCancel();
return;
}
m_request = spellCheckRequest;
}
void InputHandler::spellCheckingRequestProcessed(int32_t transactionId, spannable_string_t* spannableString)
{
#if !ENABLE_SPELLING_LOG
UNUSED_PARAM(transactionId)
#endif
SpellingLog(Platform::LogLevelWarn,
"InputHandler::spellCheckingRequestProcessed Expected transaction id %d, received %d. %s",
m_processingTransactionId,
transactionId,
transactionId == m_processingTransactionId ? "" : "We are out of sync with input service.");
if (!spannableString
|| !isActiveTextEdit()
|| !DOMSupport::elementHasContinuousSpellCheckingEnabled(m_currentFocusElement)
|| !m_processingTransactionId
|| !m_request) {
SpellingLog(Platform::LogLevelWarn, "InputHandler::spellCheckingRequestProcessed Cancelling request with transactionId %d.", transactionId);
if (m_request) {
m_request->didCancel();
m_request = 0;
}
m_processingTransactionId = -1;
return;
}
Vector<TextCheckingResult> results;
WTF::String replacement;
TextCheckingResult textCheckingResult;
textCheckingResult.type = TextCheckingTypeSpelling;
textCheckingResult.replacement = replacement;
textCheckingResult.location = 0;
textCheckingResult.length = 0;
span_t* span = spannableString->spans;
for (unsigned i = 0; i < spannableString->spans_count; i++) {
if (!span)
break;
if (span->end < span->start) {
m_request->didCancel();
m_request = 0;
return;
}
if (span->attributes_mask & MISSPELLED_WORD_ATTRIB) {
textCheckingResult.location = span->start;
textCheckingResult.length = span->end - span->start + 1;
results.append(textCheckingResult);
}
span++;
}
free(spannableString->spans);
free(spannableString);
m_request->didSucceed(results);
m_request = 0;
}
SpellChecker* InputHandler::getSpellChecker()
{
if (!m_currentFocusElement || !m_currentFocusElement->document())
return 0;
if (Frame* frame = m_currentFocusElement->document()->frame())
if (Editor* editor = frame->editor())
return editor->spellChecker();
return 0;
}
bool InputHandler::shouldRequestSpellCheckingOptionsForPoint(const Platform::IntPoint& documentContentPosition, const Element* touchedElement, imf_sp_text_t& spellCheckingOptionRequest)
{
if (!isActiveTextEdit())
return false;
Element* currentFocusElement = m_currentFocusElement.get();
if (!currentFocusElement || !currentFocusElement->isElementNode())
return false;
while (!currentFocusElement->isRootEditableElement()) {
Element* parentElement = currentFocusElement->parentElement();
if (!parentElement)
break;
currentFocusElement = parentElement;
}
if (touchedElement != currentFocusElement)
return false;
LayoutPoint contentPos(documentContentPosition);
contentPos = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), m_webPage->focusedOrMainFrame(), roundedIntPoint(contentPos));
Document* document = currentFocusElement->document();
ASSERT(document);
RenderedDocumentMarker* marker = document->markers()->renderedMarkerContainingPoint(contentPos, DocumentMarker::Spelling);
if (!marker)
return false;
m_didSpellCheckWord = true;
spellCheckingOptionRequest.startTextPosition = marker->startOffset();
spellCheckingOptionRequest.endTextPosition = marker->endOffset();
m_spellCheckingOptionsRequest.startTextPosition = 0;
m_spellCheckingOptionsRequest.endTextPosition = 0;
SpellingLog(Platform::LogLevelInfo,
"InputHandler::shouldRequestSpellCheckingOptionsForPoint Found spelling marker at point %s\nMarker start %d end %d",
documentContentPosition.toString().c_str(),
spellCheckingOptionRequest.startTextPosition,
spellCheckingOptionRequest.endTextPosition);
return true;
}
void InputHandler::requestSpellingCheckingOptions(imf_sp_text_t& spellCheckingOptionRequest, WebCore::IntSize& screenOffset, const bool shouldMoveDialog)
{
if (m_webPage->focusedOrMainFrame()->selection()->selectionType() != VisibleSelection::CaretSelection)
return;
if (!m_currentFocusElement || !m_currentFocusElement->document() || !m_currentFocusElement->document()->frame())
return;
if (shouldMoveDialog || !(spellCheckingOptionRequest.startTextPosition || spellCheckingOptionRequest.startTextPosition)) {
if (m_spellCheckingOptionsRequest.startTextPosition || m_spellCheckingOptionsRequest.endTextPosition)
spellCheckingOptionRequest = m_spellCheckingOptionsRequest;
}
if (!shouldMoveDialog && spellCheckingOptionRequest.startTextPosition == spellCheckingOptionRequest.endTextPosition)
return;
if (screenOffset.width() == -1 && screenOffset.height() == -1) {
screenOffset.setWidth(m_screenOffset.width());
screenOffset.setHeight(m_screenOffset.height());
} else {
m_screenOffset.setWidth(screenOffset.width());
m_screenOffset.setHeight(screenOffset.height());
}
WebCore::IntRect caretRect = m_webPage->focusedOrMainFrame()->selection()->selection().visibleStart().absoluteCaretBounds();
caretRect = m_webPage->focusedOrMainFrame()->view()->contentsToRootView(caretRect);
const WebCore::IntPoint scrollPosition = m_webPage->mainFrame()->view()->scrollPosition();
caretRect.move(scrollPosition.x(), scrollPosition.y());
if (!shouldMoveDialog) {
VisiblePosition caretPosition = m_currentFocusElement->document()->frame()->selection()->selection().visibleStart();
if (HTMLTextFormControlElement* controlElement = DOMSupport::toTextControlElement(m_currentFocusElement.get())) {
spellCheckingOptionRequest.startTextPosition = controlElement->indexForVisiblePosition(startOfWord(caretPosition));
spellCheckingOptionRequest.endTextPosition = controlElement->indexForVisiblePosition(endOfWord(caretPosition));
} else {
unsigned location = 0;
unsigned length = 0;
RefPtr<Range> rangeSelection = VisibleSelection(startOfWord(caretPosition), endOfWord(caretPosition)).toNormalizedRange();
if (!rangeSelection)
return;
TextIterator::getLocationAndLengthFromRange(m_currentFocusElement.get(), rangeSelection.get(), location, length);
spellCheckingOptionRequest.startTextPosition = location;
spellCheckingOptionRequest.endTextPosition = location + length;
}
}
m_spellCheckingOptionsRequest = spellCheckingOptionRequest;
InputLog(Platform::LogLevelInfo,
"InputHandler::requestSpellingCheckingOptions caretRect topLeft=%s, bottomRight=%s, startTextPosition=%d, endTextPosition=%d",
Platform::IntPoint(caretRect.minXMinYCorner()).toString().c_str(),
Platform::IntPoint(caretRect.maxXMaxYCorner()).toString().c_str(),
spellCheckingOptionRequest.startTextPosition,
spellCheckingOptionRequest.endTextPosition);
m_webPage->m_client->requestSpellingCheckingOptions(spellCheckingOptionRequest, caretRect, screenOffset, shouldMoveDialog);
}
void InputHandler::setElementUnfocused(bool refocusOccuring)
{
if (isActiveTextEdit() && DOMSupport::isElementAndDocumentAttached(m_currentFocusElement.get())) {
FocusLog(Platform::LogLevelInfo, "InputHandler::setElementUnfocused");
learnText();
finishComposition();
if (!refocusOccuring) {
notifyClientOfKeyboardVisibilityChange(false, true );
m_webPage->m_client->showFormControls(false );
}
m_webPage->m_client->inputFocusLost();
hideTextInputTypeSuggestionBox();
if (m_currentFocusElement->renderer())
m_currentFocusElement->renderer()->repaint();
FrameSelection* frameSelection = m_currentFocusElement->document()->frame()->selection();
if (frameSelection && !frameSelection->isFocused())
frameSelection->setFocused(true);
}
if (m_request) {
stopPendingSpellCheckRequests();
m_request->didCancel();
m_request = 0;
}
m_currentFocusElement = 0;
m_currentFocusElementType = TextEdit;
m_previousFocusableTextElement = 0;
m_nextFocusableTextElement = 0;
m_hasSubmitButton = false;
}
bool InputHandler::isInputModeEnabled() const
{
return m_inputModeEnabled || m_webPage->m_dumpRenderTree || Platform::Settings::instance()->alwaysShowKeyboardOnFocus();
}
void InputHandler::setInputModeEnabled(bool active)
{
FocusLog(Platform::LogLevelInfo,
"InputHandler::setInputModeEnabled '%s', override is '%s'",
active ? "true" : "false",
m_webPage->m_dumpRenderTree || Platform::Settings::instance()->alwaysShowKeyboardOnFocus() ? "true" : "false");
m_inputModeEnabled = active;
if (isInputModeEnabled()
&& isActiveTextEdit()
&& DOMSupport::isElementAndDocumentAttached(m_currentFocusElement.get())
&& !m_currentFocusElement->document()->frame()->selection()->isFocused())
m_currentFocusElement->document()->frame()->selection()->setFocused(true);
}
void InputHandler::updateFormState()
{
m_previousFocusableTextElement = 0;
m_nextFocusableTextElement = 0;
m_hasSubmitButton = false;
if (!m_currentFocusElement || !m_currentFocusElement->isFormControlElement())
return;
HTMLFormElement* formElement = static_cast<HTMLFormControlElement*>(m_currentFocusElement.get())->form();
if (!formElement)
return;
const Vector<FormAssociatedElement*> formElementList = formElement->associatedElements();
int formElementCount = formElementList.size();
if (formElementCount < 2)
return;
m_hasSubmitButton = true;
int focusTabIndex = static_cast<Node*>(m_currentFocusElement.get())->tabIndex();
int prevTabIndex = -1;
int nextTabIndex = std::numeric_limits<short>::max() + 1;
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState form has %d fields and tabIndex %d", formElementCount, focusTabIndex);
Element* firstInFieldWithoutTabIndex = 0;
Element* highestTabIndexElement = 0;
for (int focusElementId = 0; focusElementId < formElementCount; focusElementId++) {
Element* element = const_cast<HTMLElement*>(toHTMLElement(formElementList[focusElementId]));
if (element == m_currentFocusElement) {
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found focused element.");
if (focusTabIndex)
continue;
if (!m_previousFocusableTextElement) {
for (int previousElementId = focusElementId - 1; previousElementId >= 0; previousElementId--) {
Element* prevElement = const_cast<HTMLElement*>(toHTMLElement(formElementList[previousElementId]));
if (DOMSupport::isTextBasedContentEditableElement(prevElement) && !DOMSupport::isElementReadOnly(prevElement) && !static_cast<Node*>(prevElement)->tabIndex()) {
m_previousFocusableTextElement = prevElement;
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found previous element");
break;
}
}
}
if (!m_nextFocusableTextElement) {
for (int nextElementId = focusElementId + 1; nextElementId < formElementCount; nextElementId++) {
Element* nextElement = const_cast<HTMLElement*>(toHTMLElement(formElementList[nextElementId]));
if (DOMSupport::isTextBasedContentEditableElement(nextElement) && !DOMSupport::isElementReadOnly(nextElement) && !static_cast<Node*>(nextElement)->tabIndex()) {
m_nextFocusableTextElement = nextElement;
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found next element");
break;
}
}
}
} else if (focusTabIndex) {
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState processing element");
if (DOMSupport::isTextBasedContentEditableElement(element) && !DOMSupport::isElementReadOnly(element)) {
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState processing element valid");
if (int tabIndex = static_cast<Node*>(element)->tabIndex()) {
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState processing element with tab index %d", tabIndex);
if (tabIndex && tabIndex < focusTabIndex && tabIndex > prevTabIndex) {
m_previousFocusableTextElement = element;
prevTabIndex = tabIndex;
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found previous element with tabIndex %d", tabIndex);
} else if (tabIndex > focusTabIndex && tabIndex < nextTabIndex) {
m_nextFocusableTextElement = element;
nextTabIndex = tabIndex;
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState found next element with tabIndex %d", tabIndex);
}
} else if (!firstInFieldWithoutTabIndex) {
firstInFieldWithoutTabIndex = element;
}
}
} else {
if (int tabIndex = static_cast<Node*>(element)->tabIndex()) {
if (!highestTabIndexElement || (tabIndex > static_cast<Node*>(highestTabIndexElement)->tabIndex()))
highestTabIndexElement = element;
}
}
}
if (!m_nextFocusableTextElement && firstInFieldWithoutTabIndex) {
m_nextFocusableTextElement = firstInFieldWithoutTabIndex;
}
if (!m_previousFocusableTextElement && highestTabIndexElement) {
m_previousFocusableTextElement = highestTabIndexElement;
}
if (!m_nextFocusableTextElement && !m_previousFocusableTextElement) {
m_hasSubmitButton = false;
InputLog(Platform::LogLevelInfo, "InputHandler::updateFormState no valid elements found, clearing state.");
}
}
void InputHandler::focusNextField()
{
if (!m_nextFocusableTextElement)
return;
m_nextFocusableTextElement->focus();
}
void InputHandler::focusPreviousField()
{
if (!m_previousFocusableTextElement)
return;
m_previousFocusableTextElement->focus();
}
void InputHandler::submitForm()
{
if (!m_hasSubmitButton)
return;
HTMLFormElement* formElement = static_cast<HTMLFormControlElement*>(m_currentFocusElement.get())->form();
if (!formElement)
return;
InputLog(Platform::LogLevelInfo, "InputHandler::submitForm triggered");
if (elementType(m_currentFocusElement.get()) != InputTypeTextArea) {
handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_RETURN, Platform::KeyboardEvent::KeyChar, 0), false );
if (!isActiveTextEdit())
return;
}
if (formElement->checkValidity())
formElement->submit();
}
static void addInputStyleMaskForKeyboardType(int64_t& inputMask, VirtualKeyboardType keyboardType)
{
switch (keyboardType) {
case VKBTypeUrl:
inputMask |= IMF_URL_TYPE;
break;
case VKBTypePassword:
inputMask |= IMF_PASSWORD_TYPE;
break;
case VKBTypePin:
inputMask |= IMF_PIN_TYPE;
break;
case VKBTypePhone:
inputMask |= IMF_PHONE_TYPE;
break;
case VKBTypeEmail:
inputMask |= IMF_EMAIL_TYPE;
break;
default:
break;
}
}
void InputHandler::setElementFocused(Element* element)
{
ASSERT(DOMSupport::isTextBasedContentEditableElement(element));
ASSERT(element && element->document() && element->document()->frame());
#if ENABLE_SPELLING_LOG
BlackBerry::Platform::StopWatch timer;
timer.start();
#endif
if (!element || !(element->document()))
return;
Frame* frame = element->document()->frame();
if (!frame)
return;
if (frame->selection()->isFocused() != isInputModeEnabled())
frame->selection()->setFocused(isInputModeEnabled());
m_shouldEnsureFocusTextElementVisibleOnSelectionChanged = isActiveTextEdit() || DeviceInfo::instance()->hasPhysicalKeyboard();
setElementUnfocused(true );
m_currentFocusElement = element;
m_currentFocusElementType = TextEdit;
updateFormState();
if (isInputModeEnabled() && !m_delayKeyboardVisibilityChange)
m_webPage->m_client->showFormControls(m_hasSubmitButton , m_previousFocusableTextElement, m_nextFocusableTextElement);
else
m_sendFormStateOnNextKeyboardRequest = true;
BlackBerryInputType type = elementType(element);
m_currentFocusElementTextEditMask = inputStyle(type, element);
VirtualKeyboardType keyboardType = keyboardTypeAttribute(element);
if (keyboardType == VKBTypeNotSet)
keyboardType = convertInputTypeToVKBType(type);
addInputStyleMaskForKeyboardType(m_currentFocusElementTextEditMask, keyboardType);
VirtualKeyboardEnterKeyType enterKeyType = keyboardEnterKeyTypeAttribute(element);
if (enterKeyType == VKBEnterKeyNotSet && type != InputTypeTextArea) {
if (element->isFormControlElement()) {
const HTMLFormControlElement* formElement = static_cast<const HTMLFormControlElement*>(element);
if (formElement->form() && formElement->form()->defaultButton())
enterKeyType = VKBEnterKeySubmit;
}
}
FocusLog(Platform::LogLevelInfo,
"InputHandler::setElementFocused, Type=%d, Style=%lld, Keyboard Type=%d, Enter Key=%d",
type, m_currentFocusElementTextEditMask, keyboardType, enterKeyType);
m_webPage->m_client->inputFocusGained(m_currentFocusElementTextEditMask, keyboardType, enterKeyType);
handleInputLocaleChanged(m_webPage->m_webSettings->isWritingDirectionRTL());
showTextInputTypeSuggestionBox();
if (!m_delayKeyboardVisibilityChange)
notifyClientOfKeyboardVisibilityChange(true, true );
#if ENABLE_SPELLING_LOG
SpellingLog(Platform::LogLevelInfo, "InputHandler::setElementFocused Focusing the field took %f seconds.", timer.elapsed());
#endif
spellCheckTextBlock(element);
#if ENABLE_SPELLING_LOG
SpellingLog(Platform::LogLevelInfo, "InputHandler::setElementFocused Spellchecking the field increased the total time to focus to %f seconds.", timer.elapsed());
#endif
}
void InputHandler::spellCheckTextBlock(Element* element)
{
SpellingLog(Platform::LogLevelInfo, "InputHandler::spellCheckTextBlock");
if (!element) {
if (!m_currentFocusElement)
return;
element = m_currentFocusElement.get();
}
if (!shouldSpellCheckElement(element) || !isActiveTextEdit())
return;
m_spellingHandler->spellCheckTextBlock(element, TextCheckingProcessBatch);
}
bool InputHandler::shouldSpellCheckElement(const Element* element) const
{
DOMSupport::AttributeState spellCheckAttr = DOMSupport::elementSupportsSpellCheck(element);
if (spellCheckAttr == DOMSupport::Off)
return false;
if (spellCheckAttr == DOMSupport::Default && (m_currentFocusElementTextEditMask & NO_AUTO_TEXT))
return false;
return m_spellCheckStatusConfirmed ? m_globalSpellCheckStatus : true;
}
void InputHandler::stopPendingSpellCheckRequests(bool isRestartRequired)
{
m_spellingHandler->setSpellCheckActive(false);
m_processingTransactionId = 0;
if (SpellChecker* spellChecker = getSpellChecker()) {
if (spellChecker->lastRequestSequence() != spellChecker->lastProcessedSequence()) {
SpellingLog(LogLevelInfo, "InputHandler::stopPendingSpellCheckRequests will block requests up to lastRequest=%d [lastProcessed=%d]"
, spellChecker->lastRequestSequence(), spellChecker->lastProcessedSequence());
m_minimumSpellCheckingRequestSequence = spellChecker->lastRequestSequence();
if (isRestartRequired && !compositionActive()) {
spellCheckTextBlock();
}
}
}
}
void InputHandler::redrawSpellCheckDialogIfRequired(const bool shouldMoveDialog)
{
if (didSpellCheckWord()) {
imf_sp_text_t spellCheckingOptionRequest;
spellCheckingOptionRequest.startTextPosition = 0;
spellCheckingOptionRequest.endTextPosition = 0;
WebCore::IntSize screenOffset(-1, -1);
requestSpellingCheckingOptions(spellCheckingOptionRequest, screenOffset, shouldMoveDialog);
}
}
bool InputHandler::openDatePopup(HTMLInputElement* element, BlackBerryInputType type)
{
if (!element || element->isDisabledOrReadOnly() || !DOMSupport::isDateTimeInputField(element))
return false;
if (isActiveTextEdit())
clearCurrentFocusElement();
m_currentFocusElement = element;
m_currentFocusElementType = TextPopup;
switch (type) {
case BlackBerry::Platform::InputTypeDate:
case BlackBerry::Platform::InputTypeTime:
case BlackBerry::Platform::InputTypeDateTime:
case BlackBerry::Platform::InputTypeDateTimeLocal:
case BlackBerry::Platform::InputTypeMonth: {
element->document()->frame()->selection()->setCaretVisible(false);
WTF::String value = element->value();
WTF::String min = element->getAttribute(HTMLNames::minAttr).string();
WTF::String max = element->getAttribute(HTMLNames::maxAttr).string();
double step = element->getAttribute(HTMLNames::stepAttr).toDouble();
DatePickerClient* client = new DatePickerClient(type, value, min, max, step, m_webPage, element);
return m_webPage->openPagePopup(client, WebCore::IntRect());
}
default: return false;
}
}
bool InputHandler::openColorPopup(HTMLInputElement* element)
{
if (!element || element->isDisabledOrReadOnly() || !DOMSupport::isColorInputField(element))
return false;
if (isActiveTextEdit())
clearCurrentFocusElement();
m_currentFocusElement = element;
m_currentFocusElementType = TextPopup;
ColorPickerClient* client = new ColorPickerClient(element->value(), m_webPage, element);
return m_webPage->openPagePopup(client, WebCore::IntRect());
}
void InputHandler::setInputValue(const WTF::String& value)
{
if (!isActiveTextPopup())
return;
HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(m_currentFocusElement.get());
inputElement->setValue(value);
clearCurrentFocusElement();
}
void InputHandler::nodeTextChanged(const Node* node)
{
if (processingChange() || !node || node != m_currentFocusElement || !m_shouldNotifyWebView)
return;
InputLog(Platform::LogLevelInfo, "InputHandler::nodeTextChanged");
m_webPage->m_client->inputTextChanged();
removeAttributedTextMarker();
}
WebCore::IntRect InputHandler::boundingBoxForInputField()
{
if (!isActiveTextEdit())
return WebCore::IntRect();
if (!m_currentFocusElement->renderer())
return WebCore::IntRect();
if (HTMLInputElement* element = m_currentFocusElement->toInputElement()) {
if (element->isSearchField())
return element->innerBlockElement()->renderer()->absoluteBoundingBoxRect();
return m_currentFocusElement->renderer()->absoluteBoundingBoxRect();
}
if (m_currentFocusElement->hasTagName(HTMLNames::textareaTag))
return m_currentFocusElement->renderer()->absoluteBoundingBoxRect();
return WebCore::IntRect();
}
void InputHandler::ensureFocusTextElementVisible(CaretScrollType scrollType)
{
if (!isActiveTextEdit() || !isInputModeEnabled() || !m_currentFocusElement->document())
return;
if (!(Platform::Settings::instance()->allowedScrollAdjustmentForInputFields() & scrollType))
return;
if (DOMSupport::isFixedPositionOrHasFixedPositionAncestor(m_currentFocusElement->renderer()))
return;
Frame* elementFrame = m_currentFocusElement->document()->frame();
if (!elementFrame)
return;
Frame* mainFrame = m_webPage->mainFrame();
if (!mainFrame)
return;
FrameView* mainFrameView = mainFrame->view();
if (!mainFrameView)
return;
WebCore::IntRect selectionFocusRect;
switch (elementFrame->selection()->selectionType()) {
case VisibleSelection::CaretSelection:
selectionFocusRect = elementFrame->selection()->absoluteCaretBounds();
break;
case VisibleSelection::RangeSelection: {
Position selectionPosition;
if (m_webPage->m_selectionHandler->lastUpdatedEndPointIsValid())
selectionPosition = elementFrame->selection()->end();
else
selectionPosition = elementFrame->selection()->start();
selectionFocusRect = VisiblePosition(selectionPosition).absoluteCaretBounds();
break;
}
case VisibleSelection::NoSelection:
m_shouldEnsureFocusTextElementVisibleOnSelectionChanged = true;
return;
}
int fontHeight = selectionFocusRect.height();
static const int s_minimumTextHeightInPixels = Graphics::Screen::primaryScreen()->heightInMMToPixels(3);
double zoomScaleRequired;
if (m_webPage->isUserScalable() && fontHeight && fontHeight * m_webPage->currentScale() < s_minimumTextHeightInPixels && !isRunningDrt())
zoomScaleRequired = static_cast<double>(s_minimumTextHeightInPixels) / fontHeight;
else
zoomScaleRequired = m_webPage->currentScale();
if (abs(zoomScaleRequired - m_webPage->currentScale()) < zoomAnimationThreshold)
zoomScaleRequired = m_webPage->currentScale();
WebCore::FloatPoint offset(selectionFocusRect.location().x() - m_webPage->scrollPosition().x(), selectionFocusRect.location().y() - m_webPage->scrollPosition().y());
double inverseScale = zoomScaleRequired / m_webPage->currentScale();
WebCore::IntPoint destinationScrollLocation = WebCore::IntPoint(
max(0, static_cast<int>(roundf(selectionFocusRect.location().x() - offset.x() / inverseScale))),
max(0, static_cast<int>(roundf(selectionFocusRect.location().y() - offset.y() / inverseScale))));
if (elementFrame != mainFrame) { selectionFocusRect.move(-elementFrame->view()->scrollPosition().x(), -elementFrame->view()->scrollPosition().y());
if (elementFrame->ownerRenderer()) {
WebCore::IntPoint frameOffset = elementFrame->ownerRenderer()->absoluteContentBox().location();
selectionFocusRect.move(frameOffset.x(), frameOffset.y());
}
}
const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor;
if (scrollType == EdgeIfNeeded
&& (viewportAccessor->documentViewportRect().contains(selectionFocusRect))
&& zoomScaleRequired == m_webPage->currentScale()) {
return;
}
bool shouldConstrainScrollingToContentEdge = true;
Position start = elementFrame->selection()->start();
if (start.anchorNode() && start.anchorNode()->renderer()) {
if (RenderLayer* layer = start.anchorNode()->renderer()->enclosingLayer()) {
WebCore::IntRect actualScreenRect = WebCore::IntRect(destinationScrollLocation.x(), destinationScrollLocation.y(), m_webPage->actualVisibleSize().width() / inverseScale, m_webPage->actualVisibleSize().height() / inverseScale);
ScrollAlignment horizontalScrollAlignment = ScrollAlignment::alignToEdgeIfNeeded;
ScrollAlignment verticalScrollAlignment = ScrollAlignment::alignToEdgeIfNeeded;
if (scrollType != EdgeIfNeeded) {
if (RenderObject* focusedRenderer = m_currentFocusElement->renderer()) {
WebCore::IntRect nodeOutlineBounds = focusedRenderer->absoluteOutlineBounds();
WebCore::IntRect caretAtEdgeRect = rectForCaret(0);
int paddingX = abs(caretAtEdgeRect.x() - nodeOutlineBounds.x());
int paddingY = abs(caretAtEdgeRect.y() - nodeOutlineBounds.y());
if (selectionFocusRect.x() - paddingX == nodeOutlineBounds.x())
selectionFocusRect.setX(nodeOutlineBounds.x());
else if (selectionFocusRect.maxX() + paddingX == nodeOutlineBounds.maxX())
selectionFocusRect.setX(nodeOutlineBounds.maxX() - selectionFocusRect.width());
if (selectionFocusRect.y() - paddingY == nodeOutlineBounds.y())
selectionFocusRect.setY(nodeOutlineBounds.y() - selectionFocusRect.height());
else if (selectionFocusRect.maxY() + paddingY == nodeOutlineBounds.maxY())
selectionFocusRect.setY(nodeOutlineBounds.maxY() - selectionFocusRect.height());
if (selectionFocusRect.x() - caretAtEdgeRect.x() < actualScreenRect.width() / 2)
selectionFocusRect.setX(nodeOutlineBounds.x());
else
horizontalScrollAlignment = ScrollAlignment::alignCenterIfNeeded;
}
verticalScrollAlignment = (scrollType == CenterAlways) ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded;
}
static const int s_focusRectPaddingSize = Graphics::Screen::primaryScreen()->heightInMMToPixels(12);
selectionFocusRect.inflate(std::ceilf(viewportAccessor->documentFromPixelContents(Platform::FloatSize(0, s_focusRectPaddingSize)).height()));
WebCore::IntRect revealRect(layer->getRectToExpose(actualScreenRect, selectionFocusRect, horizontalScrollAlignment, verticalScrollAlignment));
shouldConstrainScrollingToContentEdge = false;
destinationScrollLocation = revealRect.location();
destinationScrollLocation.clampNegativeToZero();
WebCore::IntPoint maximumScrollPosition = WebCore::IntPoint(mainFrameView->contentsWidth() - actualScreenRect.width(), mainFrameView->contentsHeight() - actualScreenRect.height());
destinationScrollLocation = destinationScrollLocation.shrunkTo(maximumScrollPosition);
}
}
InputLog(Platform::LogLevelInfo,
"InputHandler::ensureFocusTextElementVisible zooming in to %f from %f and scrolling to point %s from %s",
zoomScaleRequired, m_webPage->currentScale(),
Platform::IntPoint(destinationScrollLocation).toString().c_str(),
Platform::IntPoint(mainFrameView->scrollPosition()).toString().c_str());
m_webPage->animateToScaleAndDocumentScrollPosition(zoomScaleRequired, WebCore::FloatPoint(destinationScrollLocation), shouldConstrainScrollingToContentEdge);
}
void InputHandler::ensureFocusPluginElementVisible()
{
if (!isActivePlugin() || !m_currentFocusElement->document())
return;
Frame* elementFrame = m_currentFocusElement->document()->frame();
if (!elementFrame)
return;
Frame* mainFrame = m_webPage->mainFrame();
if (!mainFrame)
return;
FrameView* mainFrameView = mainFrame->view();
if (!mainFrameView)
return;
WebCore::IntRect selectionFocusRect;
RenderWidget* renderWidget = static_cast<RenderWidget*>(m_currentFocusElement->renderer());
if (renderWidget) {
PluginView* pluginView = toPluginView(renderWidget->widget());
if (pluginView)
selectionFocusRect = pluginView->ensureVisibleRect();
}
if (selectionFocusRect.isEmpty())
return;
if (elementFrame != mainFrame) { selectionFocusRect.move(-elementFrame->view()->scrollPosition().x(), -elementFrame->view()->scrollPosition().y());
if (elementFrame->ownerRenderer()) {
WebCore::IntPoint frameOffset = elementFrame->ownerRenderer()->absoluteContentBox().location();
selectionFocusRect.move(frameOffset.x(), frameOffset.y());
}
}
WebCore::IntRect actualScreenRect = WebCore::IntRect(mainFrameView->scrollPosition(), m_webPage->actualVisibleSize());
if (actualScreenRect.contains(selectionFocusRect))
return;
WebCore::IntPoint pos(selectionFocusRect.center());
pos.move(-actualScreenRect.width() / 2, -actualScreenRect.height() / 2);
mainFrameView->setScrollPosition(pos);
}
void InputHandler::ensureFocusElementVisible(bool centerInView)
{
if (isActivePlugin())
ensureFocusPluginElementVisible();
else
ensureFocusTextElementVisible(centerInView ? CenterAlways : CenterIfNeeded);
}
void InputHandler::frameUnloaded(const Frame* frame)
{
if (!isActiveTextEdit())
return;
if (m_currentFocusElement->document()->frame() != frame)
return;
FocusLog(Platform::LogLevelInfo, "InputHandler::frameUnloaded");
setElementUnfocused(false );
}
void InputHandler::setDelayKeyboardVisibilityChange(bool value)
{
m_delayKeyboardVisibilityChange = value;
m_pendingKeyboardVisibilityChange = NoChange;
}
void InputHandler::processPendingKeyboardVisibilityChange()
{
if (!m_delayKeyboardVisibilityChange) {
ASSERT(m_pendingKeyboardVisibilityChange == NoChange);
return;
}
m_delayKeyboardVisibilityChange = false;
if (m_pendingKeyboardVisibilityChange == NoChange)
return;
notifyClientOfKeyboardVisibilityChange(m_pendingKeyboardVisibilityChange == Visible);
m_pendingKeyboardVisibilityChange = NoChange;
}
void InputHandler::notifyClientOfKeyboardVisibilityChange(bool visible, bool triggeredByFocusChange)
{
if (!isInputModeEnabled() && visible)
return;
if (!triggeredByFocusChange && processingChange() && visible)
return;
if (!m_delayKeyboardVisibilityChange) {
if (m_sendFormStateOnNextKeyboardRequest) {
m_webPage->m_client->showFormControls(m_hasSubmitButton , m_previousFocusableTextElement, m_nextFocusableTextElement);
m_sendFormStateOnNextKeyboardRequest = false;
}
m_webPage->showVirtualKeyboard(visible);
return;
}
m_pendingKeyboardVisibilityChange = visible ? Visible : NotVisible;
}
bool InputHandler::selectionAtStartOfElement()
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
if (!selectionStart())
return true;
return false;
}
bool InputHandler::selectionAtEndOfElement()
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
return selectionStart() == static_cast<int>(elementText().length());
}
int InputHandler::selectionStart() const
{
return selectionPosition(true);
}
int InputHandler::selectionEnd() const
{
return selectionPosition(false);
}
int InputHandler::selectionPosition(bool start) const
{
if (!m_currentFocusElement->document() || !m_currentFocusElement->document()->frame())
return 0;
if (HTMLTextFormControlElement* controlElement = DOMSupport::toTextControlElement(m_currentFocusElement.get()))
return start ? controlElement->selectionStart() : controlElement->selectionEnd();
FrameSelection caretSelection;
caretSelection.setSelection(m_currentFocusElement->document()->frame()->selection()->selection());
RefPtr<Range> rangeSelection = caretSelection.selection().toNormalizedRange();
if (!rangeSelection)
return 0;
int selectionPointInNode = start ? rangeSelection->startOffset() : rangeSelection->endOffset();
Node* containerNode = start ? rangeSelection->startContainer() : rangeSelection->endContainer();
ExceptionCode ec;
RefPtr<Range> rangeForNode = rangeOfContents(m_currentFocusElement.get());
rangeForNode->setEnd(containerNode, selectionPointInNode, ec);
ASSERT(!ec);
return TextIterator::rangeLength(rangeForNode.get());
}
void InputHandler::selectionChanged()
{
if (!m_webPage->m_mainFrame)
return;
if (!isActiveTextEdit())
return;
if (processingChange()) {
m_webPage->m_client->suppressCaretChangeNotification(true );
return;
}
if (m_shouldEnsureFocusTextElementVisibleOnSelectionChanged) {
ensureFocusTextElementVisible(EdgeIfNeeded);
m_shouldEnsureFocusTextElementVisibleOnSelectionChanged = false;
}
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
if (!m_shouldNotifyWebView)
return;
int newSelectionStart = selectionStart();
int newSelectionEnd = selectionEnd();
InputLog(Platform::LogLevelInfo,
"InputHandler::selectionChanged selectionStart=%u, selectionEnd=%u",
newSelectionStart, newSelectionEnd);
m_webPage->m_client->inputSelectionChanged(newSelectionStart, newSelectionEnd);
removeAttributedTextMarker();
}
bool InputHandler::setCursorPosition(int location)
{
return setSelection(location, location);
}
bool InputHandler::setSelection(int start, int end, bool changeIsPartOfComposition)
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
ProcessingChangeGuard guard(this);
VisibleSelection newSelection = DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), start, end);
m_currentFocusElement->document()->frame()->selection()->setSelection(newSelection, changeIsPartOfComposition ? 0 : FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle);
InputLog(Platform::LogLevelInfo,
"InputHandler::setSelection selectionStart=%u, selectionEnd=%u",
start, end);
return start == selectionStart() && end == selectionEnd();
}
WebCore::IntRect InputHandler::rectForCaret(int index)
{
if (!isActiveTextEdit())
return WebCore::IntRect();
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
if (index < 0 || index > static_cast<int>(elementText().length())) {
return WebCore::IntRect();
}
FrameSelection caretSelection;
caretSelection.setSelection(DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), index, index).visibleStart());
caretSelection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
return caretSelection.selection().visibleStart().absoluteCaretBounds();
}
void InputHandler::cancelSelection()
{
if (!isActiveTextEdit())
return;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
int selectionStartPosition = selectionStart();
ProcessingChangeGuard guard(this);
setCursorPosition(selectionStartPosition);
}
bool InputHandler::isNavigationKey(unsigned character) const
{
return character == KEYCODE_UP
|| character == KEYCODE_DOWN
|| character == KEYCODE_LEFT
|| character == KEYCODE_RIGHT;
}
bool InputHandler::handleKeyboardInput(const Platform::KeyboardEvent& keyboardEvent, bool changeIsPartOfComposition)
{
InputLog(Platform::LogLevelInfo,
"InputHandler::handleKeyboardInput received character='%c', type=%d",
keyboardEvent.character(), keyboardEvent.type());
m_shouldNotifyWebView = true;
setInputModeEnabled();
Platform::KeyboardEvent::Type type = keyboardEvent.type();
if (type == Platform::KeyboardEvent::KeyUp) {
if (m_expectedKeyUpChar == keyboardEvent.character() || (isASCIIUpper(m_expectedKeyUpChar) && m_expectedKeyUpChar == toASCIIUpper(keyboardEvent.character()))) {
m_expectedKeyUpChar = 0;
changeIsPartOfComposition = true;
}
}
if (!changeIsPartOfComposition && compositionActive()) {
if (type == Platform::KeyboardEvent::KeyDown && isNavigationKey(keyboardEvent.character()))
removeAttributedTextMarker();
else
return false;
}
ProcessingChangeGuard guard(this);
unsigned adjustedModifiers = keyboardEvent.modifiers();
if (WTF::isASCIIUpper(keyboardEvent.character()))
adjustedModifiers |= KEYMOD_SHIFT;
ASSERT(m_webPage->m_page->focusController());
bool keyboardEventHandled = false;
if (Frame* focusedFrame = m_webPage->m_page->focusController()->focusedFrame()) {
bool isKeyChar = type == Platform::KeyboardEvent::KeyChar;
if (isKeyChar)
type = Platform::KeyboardEvent::KeyDown;
else if (type == Platform::KeyboardEvent::KeyDown) {
m_expectedKeyUpChar = keyboardEvent.character();
m_shouldNotifyWebView = shouldNotifyWebView(keyboardEvent);
}
Platform::KeyboardEvent adjustedKeyboardEvent(keyboardEvent.character(), type, adjustedModifiers, keyboardEvent.keycode(), keyboardEvent.alternateCharacter(), keyboardEvent.sourceDevice());
keyboardEventHandled = focusedFrame->eventHandler()->keyEvent(PlatformKeyboardEvent(adjustedKeyboardEvent));
m_shouldNotifyWebView = true;
if (isKeyChar) {
type = Platform::KeyboardEvent::KeyUp;
adjustedKeyboardEvent = Platform::KeyboardEvent(keyboardEvent.character(), type, adjustedModifiers, keyboardEvent.keycode(), keyboardEvent.alternateCharacter(), keyboardEvent.sourceDevice());
keyboardEventHandled = focusedFrame->eventHandler()->keyEvent(PlatformKeyboardEvent(adjustedKeyboardEvent)) || keyboardEventHandled;
}
if (!changeIsPartOfComposition && type == Platform::KeyboardEvent::KeyUp)
ensureFocusTextElementVisible(EdgeIfNeeded);
}
if (m_currentFocusElement && keyboardEventHandled)
showTextInputTypeSuggestionBox();
return keyboardEventHandled;
}
bool InputHandler::shouldNotifyWebView(const Platform::KeyboardEvent& keyboardEvent)
{
return !(keyboardEvent.character() == KEYCODE_BACKSPACE || keyboardEvent.character() == KEYCODE_RETURN || keyboardEvent.character() == KEYCODE_KP_ENTER);
}
bool InputHandler::deleteSelection()
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
Frame* frame = m_currentFocusElement->document()->frame();
if (frame->selection()->selectionType() != VisibleSelection::RangeSelection)
return false;
ASSERT(frame->editor());
if (!handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), false ))
return false;
selectionChanged();
return true;
}
void InputHandler::insertText(const WTF::String& string)
{
if (!isActiveTextEdit())
return;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->editor());
Editor* editor = m_currentFocusElement->document()->frame()->editor();
editor->command("InsertText").execute(string);
}
void InputHandler::clearField()
{
if (!isActiveTextEdit())
return;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->editor());
Editor* editor = m_currentFocusElement->document()->frame()->editor();
editor->command("SelectAll").execute();
editor->command("DeleteBackward").execute();
}
bool InputHandler::executeTextEditCommand(const WTF::String& commandName)
{
ASSERT(m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->editor());
Editor* editor = m_webPage->focusedOrMainFrame()->editor();
return editor->command(commandName).execute();
}
void InputHandler::cut()
{
executeTextEditCommand("Cut");
}
void InputHandler::copy()
{
executeTextEditCommand("Copy");
}
void InputHandler::paste()
{
executeTextEditCommand("Paste");
}
void InputHandler::selectAll()
{
executeTextEditCommand("SelectAll");
}
void InputHandler::addAttributedTextMarker(int start, int end, const AttributeTextStyle& style)
{
if ((end - start) < 1 || end > static_cast<int>(elementText().length()))
return;
RefPtr<Range> markerRange = DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), start, end).toNormalizedRange();
m_currentFocusElement->document()->markers()->addMarker(markerRange.get(), DocumentMarker::AttributeText, WTF::String("Input Marker"), style);
}
void InputHandler::removeAttributedTextMarker()
{
if (m_currentFocusElement && m_currentFocusElement->document())
m_currentFocusElement->document()->markers()->removeMarkers(DocumentMarker::AttributeText);
m_composingTextStart = 0;
m_composingTextEnd = 0;
}
void InputHandler::clearCurrentFocusElement()
{
if (m_currentFocusElement)
m_currentFocusElement->blur();
}
bool InputHandler::willOpenPopupForNode(Node* node)
{
if (!node)
return false;
ASSERT(!node->isInShadowTree());
if (node->hasTagName(HTMLNames::selectTag) || node->hasTagName(HTMLNames::optionTag)) {
return true;
}
if (node->isElementNode()) {
Element* element = toElement(node);
if (DOMSupport::isPopupInputField(element))
return true;
}
return false;
}
bool InputHandler::didNodeOpenPopup(Node* node)
{
if (!node)
return false;
ASSERT(!node->isInShadowTree());
if (node->hasTagName(HTMLNames::selectTag))
return openSelectPopup(static_cast<HTMLSelectElement*>(node));
if (node->hasTagName(HTMLNames::optionTag)) {
HTMLOptionElement* optionElement = static_cast<HTMLOptionElement*>(node);
return openSelectPopup(optionElement->ownerSelectElement());
}
if (HTMLInputElement* element = node->toInputElement()) {
if (DOMSupport::isDateTimeInputField(element))
return openDatePopup(element, elementType(element));
if (DOMSupport::isColorInputField(element))
return openColorPopup(element);
}
return false;
}
bool InputHandler::openSelectPopup(HTMLSelectElement* select)
{
if (!select || select->isDisabledFormControl())
return false;
if (!select->document()->view())
return false;
if (isActiveTextEdit())
clearCurrentFocusElement();
m_currentFocusElement = select;
m_currentFocusElementType = SelectPopup;
const WTF::Vector<HTMLElement*>& listItems = select->listItems();
int size = listItems.size();
bool multiple = select->multiple();
ScopeArray<BlackBerry::Platform::String> labels;
labels.reset(new BlackBerry::Platform::String[size]);
bool* enableds = 0;
int* itemTypes = 0;
bool* selecteds = 0;
if (size) {
enableds = new bool[size];
itemTypes = new int[size];
selecteds = new bool[size];
for (int i = 0; i < size; i++) {
if (listItems[i]->hasTagName(HTMLNames::optionTag)) {
HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems[i]);
labels[i] = option->textIndentedToRespectGroupLabel();
enableds[i] = option->isDisabledFormControl() ? 0 : 1;
selecteds[i] = option->selected();
itemTypes[i] = option->parentNode() && option->parentNode()->hasTagName(HTMLNames::optgroupTag) ? TypeOptionInGroup : TypeOption;
} else if (listItems[i]->hasTagName(HTMLNames::optgroupTag)) {
HTMLOptGroupElement* optGroup = static_cast<HTMLOptGroupElement*>(listItems[i]);
labels[i] = optGroup->groupLabelText();
enableds[i] = optGroup->isDisabledFormControl() ? 0 : 1;
selecteds[i] = false;
itemTypes[i] = TypeGroup;
} else if (listItems[i]->hasTagName(HTMLNames::hrTag)) {
enableds[i] = false;
selecteds[i] = false;
itemTypes[i] = TypeSeparator;
}
}
}
SelectPopupClient* selectClient = new SelectPopupClient(multiple, size, labels, enableds, itemTypes, selecteds, m_webPage, select);
WebCore::IntRect elementRectInRootView = select->document()->view()->contentsToRootView(enclosingIntRect(select->getRect()));
if (!m_webPage->openPagePopup(selectClient, elementRectInRootView))
m_webPage->m_client->openPopupList(multiple, size, labels, enableds, itemTypes, selecteds);
delete[] enableds;
delete[] itemTypes;
delete[] selecteds;
return true;
}
void InputHandler::setPopupListIndex(int index)
{
if (index == -2) return clearCurrentFocusElement();
if (!isActiveSelectPopup())
return clearCurrentFocusElement();
RenderObject* renderer = m_currentFocusElement->renderer();
if (renderer && renderer->isMenuList()) {
RenderMenuList* renderMenu = toRenderMenuList(renderer);
renderMenu->hidePopup();
}
HTMLSelectElement* selectElement = static_cast<HTMLSelectElement*>(m_currentFocusElement.get());
int optionIndex = selectElement->listToOptionIndex(index);
selectElement->optionSelectedByUser(optionIndex, true , true );
clearCurrentFocusElement();
}
void InputHandler::setPopupListIndexes(int size, const bool* selecteds)
{
if (!isActiveSelectPopup())
return clearCurrentFocusElement();
if (size < 0)
return;
HTMLSelectElement* selectElement = static_cast<HTMLSelectElement*>(m_currentFocusElement.get());
const WTF::Vector<HTMLElement*>& items = selectElement->listItems();
if (items.size() != static_cast<unsigned>(size))
return;
HTMLOptionElement* option;
for (int i = 0; i < size; i++) {
if (items[i]->hasTagName(HTMLNames::optionTag)) {
option = static_cast<HTMLOptionElement*>(items[i]);
option->setSelectedState(selecteds[i]);
}
}
selectElement->dispatchFormControlChangeEvent();
selectElement->renderer()->repaint();
clearCurrentFocusElement();
}
bool InputHandler::setBatchEditingActive(bool active)
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document());
ASSERT(m_currentFocusElement->document()->frame());
BackingStoreClient* backingStoreClient = m_webPage->backingStoreClient();
ASSERT(backingStoreClient);
if (!active) {
backingStoreClient->backingStore()->resumeBackingStoreUpdates();
backingStoreClient->backingStore()->resumeScreenUpdates(BackingStore::RenderAndBlit);
} else {
backingStoreClient->backingStore()->suspendBackingStoreUpdates();
backingStoreClient->backingStore()->suspendScreenUpdates();
}
return true;
}
bool InputHandler::isCaretAtEndOfText()
{
return caretPosition() == static_cast<int>(elementText().length());
}
int InputHandler::caretPosition() const
{
if (!isActiveTextEdit())
return -1;
return selectionStart();
}
int relativeLeftOffset(int caretPosition, int leftOffset)
{
ASSERT(caretPosition >= 0);
return std::max(0, caretPosition - leftOffset);
}
int relativeRightOffset(int caretPosition, unsigned totalLengthOfText, int rightOffset)
{
ASSERT(caretPosition >= 0);
ASSERT(totalLengthOfText < INT_MAX);
return std::min(caretPosition + rightOffset, static_cast<int>(totalLengthOfText));
}
bool InputHandler::deleteTextRelativeToCursor(int leftOffset, int rightOffset)
{
if (!isActiveTextEdit() || compositionActive())
return false;
InputLog(Platform::LogLevelInfo,
"InputHandler::deleteTextRelativeToCursor left %d right %d",
leftOffset, rightOffset);
int caretOffset = caretPosition();
int start = relativeLeftOffset(caretOffset, leftOffset);
int end = relativeRightOffset(caretOffset, elementText().length(), rightOffset);
if (leftOffset == 1 && !rightOffset) {
if (selectionActive())
return deleteSelection();
if (!handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), true ))
return false;
} else if (!deleteText(start, end))
return false;
ProcessingChangeGuard guard(this);
ensureFocusTextElementVisible(EdgeIfNeeded);
return true;
}
bool InputHandler::deleteText(int start, int end)
{
if (!isActiveTextEdit())
return false;
{
ProcessingChangeGuard guard(this);
if (end - start == 1)
return handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), true );
if (!setSelection(start, end, true ))
return false;
}
InputLog(Platform::LogLevelInfo, "InputHandler::deleteText start %d end %d", start, end);
return deleteSelection();
}
spannable_string_t* InputHandler::spannableTextInRange(int start, int end, int32_t)
{
if (!isActiveTextEdit())
return 0;
if (start >= end)
return 0;
int length = end - start;
WTF::String textString = elementText().substring(start, length);
spannable_string_t* pst = (spannable_string_t*)fastMalloc(sizeof(spannable_string_t));
pst->str = (wchar_t*)malloc(sizeof(wchar_t) * (length + 1));
if (!pst->str) {
Platform::logAlways(Platform::LogLevelCritical, "InputHandler::spannableTextInRange Cannot allocate memory for string.");
free(pst);
return 0;
}
int stringLength = 0;
if (!convertStringToWchar(textString, pst->str, length + 1, &stringLength)) {
Platform::logAlways(Platform::LogLevelCritical, "InputHandler::spannableTextInRange failed to convert string.");
free(pst->str);
free(pst);
return 0;
}
pst->length = stringLength;
pst->spans_count = 0;
pst->spans = 0;
return pst;
}
spannable_string_t* InputHandler::selectedText(int32_t flags)
{
if (!isActiveTextEdit())
return 0;
return spannableTextInRange(selectionStart(), selectionEnd(), flags);
}
spannable_string_t* InputHandler::textBeforeCursor(int32_t length, int32_t flags)
{
if (!isActiveTextEdit())
return 0;
int caretOffset = caretPosition();
int start = relativeLeftOffset(caretOffset, length);
int end = caretOffset;
return spannableTextInRange(start, end, flags);
}
spannable_string_t* InputHandler::textAfterCursor(int32_t length, int32_t flags)
{
if (!isActiveTextEdit())
return 0;
int caretOffset = caretPosition();
int start = caretOffset;
int end = relativeRightOffset(caretOffset, elementText().length(), length);
return spannableTextInRange(start, end, flags);
}
extracted_text_t* InputHandler::extractedTextRequest(extracted_text_request_t*, int32_t flags)
{
if (!isActiveTextEdit())
return 0;
extracted_text_t* extractedText = (extracted_text_t *)fastMalloc(sizeof(extracted_text_t));
extractedText->text = spannableTextInRange(0, elementText().length(), flags);
extractedText->partial_start_offset = 0;
extractedText->partial_end_offset = 0;
extractedText->start_offset = 0;
extractedText->selection_start = selectionStart() - extractedText->start_offset;
extractedText->selection_end = selectionEnd() - extractedText->start_offset;
bool selectionActive = extractedText->selection_start != extractedText->selection_end;
bool singleLine = m_currentFocusElement->hasTagName(HTMLNames::inputTag);
extractedText->flags = selectionActive & singleLine;
return extractedText;
}
static void addCompositionTextStyleToAttributeTextStyle(AttributeTextStyle& style)
{
style.setUnderline(AttributeTextStyle::StandardUnderline);
}
static void addActiveTextStyleToAttributeTextStyle(AttributeTextStyle& style)
{
style.setBackgroundColor(Color("blue"));
style.setTextColor(Color("white"));
}
static AttributeTextStyle compositionTextStyle()
{
AttributeTextStyle style;
addCompositionTextStyleToAttributeTextStyle(style);
return style;
}
static AttributeTextStyle textStyleFromMask(int64_t mask)
{
AttributeTextStyle style;
if (mask & COMPOSED_TEXT_ATTRIB)
addCompositionTextStyleToAttributeTextStyle(style);
if (mask & ACTIVE_REGION_ATTRIB)
addActiveTextStyleToAttributeTextStyle(style);
return style;
}
bool InputHandler::removeComposedText()
{
if (compositionActive()) {
if (!deleteText(m_composingTextStart, m_composingTextEnd)) {
return false;
}
removeAttributedTextMarker();
}
return true;
}
int32_t InputHandler::setComposingRegion(int32_t start, int32_t end)
{
if (!isActiveTextEdit())
return -1;
if (!removeComposedText()) {
return -1;
}
m_composingTextStart = start;
m_composingTextEnd = end;
if (compositionActive())
addAttributedTextMarker(start, end, compositionTextStyle());
InputLog(Platform::LogLevelInfo, "InputHandler::setComposingRegion start %d end %d", start, end);
return 0;
}
int32_t InputHandler::finishComposition()
{
if (!isActiveTextEdit())
return -1;
if (!compositionActive())
return 0;
removeAttributedTextMarker();
InputLog(Platform::LogLevelInfo, "InputHandler::finishComposition completed");
return 0;
}
span_t* InputHandler::firstSpanInString(spannable_string_t* spannableString, SpannableStringAttribute attrib)
{
span_t* span = spannableString->spans;
for (unsigned i = 0; i < spannableString->spans_count; i++) {
if (span->attributes_mask & attrib)
return span;
span++;
}
return 0;
}
bool InputHandler::isTrailingSingleCharacter(span_t* span, unsigned stringLength, unsigned composingTextLength)
{
if (composingTextLength != stringLength - 1)
return false;
if (!span)
return false;
if (span->start == span->end) {
if (span->start == stringLength - 1)
return true;
}
return false;
}
bool InputHandler::setText(spannable_string_t* spannableString)
{
if (!isActiveTextEdit() || !spannableString)
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
Frame* frame = m_currentFocusElement->document()->frame();
Editor* editor = frame->editor();
ASSERT(editor);
m_webPage->m_selectionHandler->setSelectionActive(false);
WTF::String textToInsert = convertSpannableStringToString(spannableString);
int textLength = textToInsert.length();
InputLog(Platform::LogLevelInfo,
"InputHandler::setText spannableString is '%s', of length %d",
textToInsert.latin1().data(), textLength);
span_t* changedSpan = firstSpanInString(spannableString, CHANGED_ATTRIB);
int composingTextStart = m_composingTextStart;
int composingTextEnd = m_composingTextEnd;
int composingTextLength = compositionLength();
removeAttributedTextMarker();
if (isTrailingSingleCharacter(changedSpan, textLength, composingTextLength)) {
if (firstSpanInString(spannableString, UNCONVERTED_TEXT_ATTRIB)) {
InputLog(Platform::LogLevelInfo, "InputHandler::setText Single trailing character detected. Text is unconverted.");
return editor->command("InsertText").execute(textToInsert.right(1));
}
InputLog(Platform::LogLevelInfo, "InputHandler::setText Single trailing character detected.");
return handleKeyboardInput(Platform::KeyboardEvent(textToInsert[textLength - 1], Platform::KeyboardEvent::KeyDown, 0), false );
}
if (!changedSpan) {
if (composingTextLength == textLength) {
InputLog(Platform::LogLevelInfo, "InputHandler::setText No spans have changed. New text is the same length as the old. Nothing to do.");
return true;
}
if (composingTextLength - textLength == 1) {
InputLog(Platform::LogLevelInfo, "InputHandler::setText No spans have changed. New text is one character shorter than the old. Treating as 'delete'.");
return handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), true );
}
}
if (composingTextLength && !setSelection(composingTextStart, composingTextEnd, true ))
return false;
if (!textLength) {
if (selectionActive())
return handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_BACKSPACE, Platform::KeyboardEvent::KeyDown, 0), true );
return true;
}
bool requiresSpaceKeyPress = false;
if (textLength > 0 && textToInsert[textLength - 1] == KEYCODE_SPACE) {
requiresSpaceKeyPress = true;
textLength--;
textToInsert.remove(textLength, 1);
}
InputLog(Platform::LogLevelInfo,
"InputHandler::setText Request being processed. Text before processing: '%s'",
elementText().latin1().data());
if (textLength == 1 && !spannableString->spans_count) {
InputLog(Platform::LogLevelInfo, "InputHandler::setText Single character entry treated as key-press in the absense of spans.");
return handleKeyboardInput(Platform::KeyboardEvent(textToInsert[0], Platform::KeyboardEvent::KeyDown, 0), true );
}
if (!textToInsert.isEmpty() && !editor->command("InsertText").execute(textToInsert)) {
InputLog(Platform::LogLevelWarn,
"InputHandler::setText Failed to insert text '%s'",
textToInsert.latin1().data());
return false;
}
if (requiresSpaceKeyPress)
handleKeyboardInput(Platform::KeyboardEvent(KEYCODE_SPACE, Platform::KeyboardEvent::KeyDown, 0), true );
InputLog(Platform::LogLevelInfo,
"InputHandler::setText Request being processed. Text after processing '%s'",
elementText().latin1().data());
return true;
}
bool InputHandler::setTextAttributes(int insertionPoint, spannable_string_t* spannableString)
{
span_t* span = spannableString->spans;
for (unsigned i = 0; i < spannableString->spans_count; i++) {
unsigned startPosition = insertionPoint + span->start;
unsigned endPosition = insertionPoint + span->end + 1;
if (endPosition < startPosition || endPosition > elementText().length())
return false;
if (!span->attributes_mask)
continue;
InputLog(Platform::LogLevelInfo,
"InputHandler::setTextAttributes adding marker %d to %d - %llu",
startPosition, endPosition, span->attributes_mask);
addAttributedTextMarker(startPosition, endPosition, textStyleFromMask(span->attributes_mask));
span++;
}
InputLog(Platform::LogLevelInfo, "InputHandler::setTextAttributes attribute count %d", spannableString->spans_count);
return true;
}
bool InputHandler::setRelativeCursorPosition(int insertionPoint, int relativeCursorPosition)
{
if (!isActiveTextEdit())
return false;
if (relativeCursorPosition == 1) {
m_currentFocusElement->document()->frame()->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
return true;
}
int cursorPosition = 0;
if (relativeCursorPosition <= 0) {
cursorPosition = insertionPoint + relativeCursorPosition;
} else {
cursorPosition = caretPosition() + relativeCursorPosition - 1;
}
if (cursorPosition < 0 || cursorPosition > (int)elementText().length())
return false;
InputLog(Platform::LogLevelInfo,
"InputHandler::setRelativeCursorPosition cursor position %d",
cursorPosition);
return setCursorPosition(cursorPosition);
}
bool InputHandler::setSpannableTextAndRelativeCursor(spannable_string_t* spannableString, int relativeCursorPosition, bool markTextAsComposing)
{
InputLog(Platform::LogLevelInfo,
"InputHandler::setSpannableTextAndRelativeCursor(%d, %d, %d)",
spannableString->length, relativeCursorPosition, markTextAsComposing);
int insertionPoint = compositionActive() ? m_composingTextStart : selectionStart();
ProcessingChangeGuard guard(this);
if (!setText(spannableString))
return false;
if (!setTextAttributes(insertionPoint, spannableString))
return false;
if (!setRelativeCursorPosition(insertionPoint, relativeCursorPosition))
return false;
if (markTextAsComposing) {
m_composingTextStart = insertionPoint;
m_composingTextEnd = insertionPoint + spannableString->length;
}
return true;
}
int32_t InputHandler::setComposingText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
{
if (!isActiveTextEdit())
return -1;
if (!spannableString)
return -1;
InputLog(Platform::LogLevelInfo,
"InputHandler::setComposingText at relativeCursorPosition: %d",
relativeCursorPosition);
setInputModeEnabled();
return setSpannableTextAndRelativeCursor(spannableString, relativeCursorPosition, true ) ? 0 : -1;
}
int32_t InputHandler::commitText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
{
if (!isActiveTextEdit())
return -1;
if (!spannableString)
return -1;
InputLog(Platform::LogLevelInfo, "InputHandler::commitText");
return setSpannableTextAndRelativeCursor(spannableString, relativeCursorPosition, false ) ? 0 : -1;
}
void InputHandler::restoreViewState()
{
setInputModeEnabled();
m_webPage->m_selectionHandler->selectionPositionChanged();
}
void InputHandler::showTextInputTypeSuggestionBox(bool allowEmptyPrefix)
{
if (!isActiveTextEdit())
return;
HTMLInputElement* focusedInputElement = static_cast<HTMLInputElement*>(m_currentFocusElement->toInputElement());
if (!focusedInputElement)
return;
if (!m_suggestionDropdownBoxHandler)
m_suggestionDropdownBoxHandler = SuggestionBoxHandler::create(focusedInputElement);
if ((m_suggestionDropdownBoxHandler->focusedElement()) && m_suggestionDropdownBoxHandler->focusedElement() != focusedInputElement)
m_suggestionDropdownBoxHandler->setInputElementAndUpdateDisplay(focusedInputElement);
else
m_suggestionDropdownBoxHandler->showDropdownBox(allowEmptyPrefix);
}
void InputHandler::hideTextInputTypeSuggestionBox()
{
if (m_suggestionDropdownBoxHandler)
m_suggestionDropdownBoxHandler->hideDropdownBox();
}
void InputHandler::elementTouched(WebCore::Element* nonShadowElementUnderFatFinger)
{
if (isActiveTextEdit() && nonShadowElementUnderFatFinger == m_currentFocusElement)
showTextInputTypeSuggestionBox(true );
m_elementTouchedIsCrossFrame = nonShadowElementUnderFatFinger
&& nonShadowElementUnderFatFinger->document()
&& nonShadowElementUnderFatFinger->document()->frame() != m_webPage->focusedOrMainFrame();
}
}
}