/** * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "config.h" #if ENABLE(WML) #include "WMLInputElement.h" #include "EventNames.h" #include "FormDataList.h" #include "Frame.h" #include "HTMLNames.h" #include "KeyboardEvent.h" #include "MappedAttribute.h" #include "RenderTextControlSingleLine.h" #include "TextEvent.h" #include "WMLDocument.h" #include "WMLNames.h" #include "WMLPageState.h" namespace WebCore { WMLInputElement::WMLInputElement(const QualifiedName& tagName, Document* doc) : WMLFormControlElement(tagName, doc) , m_isPasswordField(false) , m_isEmptyOk(false) , m_numOfCharsAllowedByMask(0) { } WMLInputElement::~WMLInputElement() { if (m_isPasswordField) document()->unregisterForDocumentActivationCallbacks(this); } static const AtomicString& formatCodes() { DEFINE_STATIC_LOCAL(AtomicString, codes, ("AaNnXxMm")); return codes; } bool WMLInputElement::isKeyboardFocusable(KeyboardEvent*) const { return WMLFormControlElement::isFocusable(); } bool WMLInputElement::isMouseFocusable() const { return WMLFormControlElement::isFocusable(); } void WMLInputElement::dispatchFocusEvent() { InputElement::dispatchFocusEvent(m_data, this, this); WMLElement::dispatchFocusEvent(); } void WMLInputElement::dispatchBlurEvent() { // Firstly check if it is allowed to leave this input field String val = value(); if ((!m_isEmptyOk && val.isEmpty()) || !isConformedToInputMask(val)) { updateFocusAppearance(true); return; } // update the name variable of WML input elmenet String nameVariable = formControlName(); if (!nameVariable.isEmpty()) wmlPageStateForDocument(document())->storeVariable(nameVariable, val); InputElement::dispatchBlurEvent(m_data, this, this); WMLElement::dispatchBlurEvent(); } void WMLInputElement::updateFocusAppearance(bool restorePreviousSelection) { InputElement::updateFocusAppearance(m_data, this, this, restorePreviousSelection); } void WMLInputElement::aboutToUnload() { InputElement::aboutToUnload(this, this); } int WMLInputElement::size() const { return m_data.size(); } const AtomicString& WMLInputElement::formControlType() const { // needs to be lowercase according to DOM spec if (m_isPasswordField) { DEFINE_STATIC_LOCAL(const AtomicString, password, ("password")); return password; } DEFINE_STATIC_LOCAL(const AtomicString, text, ("text")); return text; } const AtomicString& WMLInputElement::formControlName() const { return m_data.name(); } String WMLInputElement::value() const { String value = m_data.value(); if (value.isNull()) value = constrainValue(getAttribute(HTMLNames::valueAttr)); return value; } void WMLInputElement::setValue(const String& value) { InputElement::updatePlaceholderVisibility(m_data, this, this); setFormControlValueMatchesRenderer(false); m_data.setValue(constrainValue(value)); if (inDocument()) document()->updateStyleIfNeeded(); if (renderer()) renderer()->updateFromElement(); setNeedsStyleRecalc(); unsigned max = m_data.value().length(); if (document()->focusedNode() == this) InputElement::updateSelectionRange(this, this, max, max); else cacheSelection(max, max); InputElement::notifyFormStateChanged(this); } void WMLInputElement::setValueFromRenderer(const String& value) { InputElement::setValueFromRenderer(m_data, this, this, value); } bool WMLInputElement::saveFormControlState(String& result) const { if (m_isPasswordField) return false; result = value(); return true; } void WMLInputElement::restoreFormControlState(const String& state) { ASSERT(!m_isPasswordField); // should never save/restore password fields setValue(state); } void WMLInputElement::select() { if (RenderTextControl* r = toRenderTextControl(renderer())) r->select(); } void WMLInputElement::accessKeyAction(bool) { // should never restore previous selection here focus(false); } void WMLInputElement::parseMappedAttribute(MappedAttribute* attr) { if (attr->name() == HTMLNames::nameAttr) m_data.setName(parseValueForbiddingVariableReferences(attr->value())); else if (attr->name() == HTMLNames::typeAttr) { String type = parseValueForbiddingVariableReferences(attr->value()); m_isPasswordField = (type == "password"); } else if (attr->name() == HTMLNames::valueAttr) { // We only need to setChanged if the form is looking at the default value right now. if (m_data.value().isNull()) setNeedsStyleRecalc(); setFormControlValueMatchesRenderer(false); } else if (attr->name() == HTMLNames::maxlengthAttr) InputElement::parseMaxLengthAttribute(m_data, this, this, attr); else if (attr->name() == HTMLNames::sizeAttr) InputElement::parseSizeAttribute(m_data, this, attr); else if (attr->name() == WMLNames::formatAttr) m_formatMask = validateInputMask(parseValueForbiddingVariableReferences(attr->value())); else if (attr->name() == WMLNames::emptyokAttr) m_isEmptyOk = (attr->value() == "true"); else WMLElement::parseMappedAttribute(attr); // FIXME: Handle 'accesskey' attribute // FIXME: Handle 'tabindex' attribute // FIXME: Handle 'title' attribute } void WMLInputElement::copyNonAttributeProperties(const Element* source) { const WMLInputElement* sourceElement = static_cast(source); m_data.setValue(sourceElement->m_data.value()); WMLElement::copyNonAttributeProperties(source); } RenderObject* WMLInputElement::createRenderer(RenderArena* arena, RenderStyle*) { return new (arena) RenderTextControlSingleLine(this); } void WMLInputElement::detach() { WMLElement::detach(); setFormControlValueMatchesRenderer(false); } bool WMLInputElement::appendFormData(FormDataList& encoding, bool) { if (formControlName().isEmpty()) return false; encoding.appendData(formControlName(), value()); return true; } void WMLInputElement::reset() { setValue(String()); } void WMLInputElement::defaultEventHandler(Event* evt) { bool clickDefaultFormButton = false; if (evt->type() == eventNames().textInputEvent && evt->isTextEvent()) { TextEvent* textEvent = static_cast(evt); if (textEvent->data() == "\n") clickDefaultFormButton = true; else if (renderer() && !isConformedToInputMask(textEvent->data()[0], toRenderTextControl(renderer())->text().length() + 1)) // If the inputed char doesn't conform to the input mask, stop handling return; } if (evt->type() == eventNames().keydownEvent && evt->isKeyboardEvent() && focused() && document()->frame() && document()->frame()->doTextFieldCommandFromEvent(this, static_cast(evt))) { evt->setDefaultHandled(); return; } // Let the key handling done in EventTargetNode take precedence over the event handling here for editable text fields if (!clickDefaultFormButton) { WMLElement::defaultEventHandler(evt); if (evt->defaultHandled()) return; } // Use key press event here since sending simulated mouse events // on key down blocks the proper sending of the key press event. if (evt->type() == eventNames().keypressEvent && evt->isKeyboardEvent()) { // Simulate mouse click on the default form button for enter for these types of elements. if (static_cast(evt)->charCode() == '\r') clickDefaultFormButton = true; } if (clickDefaultFormButton) { // Fire onChange for text fields. RenderObject* r = renderer(); if (r && toRenderTextControl(r)->isEdited()) { dispatchEvent(eventNames().changeEvent, true, false); // Refetch the renderer since arbitrary JS code run during onchange can do anything, including destroying it. r = renderer(); if (r) toRenderTextControl(r)->setEdited(false); } evt->setDefaultHandled(); return; } if (evt->isBeforeTextInsertedEvent()) InputElement::handleBeforeTextInsertedEvent(m_data, this, document(), evt); if (renderer() && (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent() || evt->type() == eventNames().blurEvent || evt->type() == eventNames().focusEvent)) static_cast(renderer())->forwardEvent(evt); } void WMLInputElement::cacheSelection(int start, int end) { m_data.setCachedSelectionStart(start); m_data.setCachedSelectionEnd(end); } String WMLInputElement::constrainValue(const String& proposedValue) const { return InputElement::constrainValue(this, proposedValue, m_data.maxLength()); } void WMLInputElement::documentDidBecomeActive() { ASSERT(m_isPasswordField); reset(); } bool WMLInputElement::placeholderShouldBeVisible() const { return m_data.placeholderShouldBeVisible(); } void WMLInputElement::willMoveToNewOwnerDocument() { // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered if (m_isPasswordField) document()->unregisterForDocumentActivationCallbacks(this); WMLElement::willMoveToNewOwnerDocument(); } void WMLInputElement::didMoveToNewOwnerDocument() { if (m_isPasswordField) document()->registerForDocumentActivationCallbacks(this); WMLElement::didMoveToNewOwnerDocument(); } void WMLInputElement::initialize() { String nameVariable = formControlName(); String variableValue; WMLPageState* pageSate = wmlPageStateForDocument(document()); ASSERT(pageSate); if (!nameVariable.isEmpty()) variableValue = pageSate->getVariable(nameVariable); if (variableValue.isEmpty() || !isConformedToInputMask(variableValue)) { String val = value(); if (isConformedToInputMask(val)) variableValue = val; else variableValue = ""; pageSate->storeVariable(nameVariable, variableValue); } setValue(variableValue); if (!hasAttribute(WMLNames::emptyokAttr)) { if (m_formatMask.isEmpty() || // check if the format codes is just "*f" (m_formatMask.length() == 2 && m_formatMask[0] == '*' && formatCodes().find(m_formatMask[1]) != -1)) m_isEmptyOk = true; } } String WMLInputElement::validateInputMask(const String& inputMask) { bool isValid = true; bool hasWildcard = false; unsigned escapeCharCount = 0; unsigned maskLength = inputMask.length(); UChar formatCode; for (unsigned i = 0; i < maskLength; ++i) { formatCode = inputMask[i]; if (formatCodes().find(formatCode) == -1) { if (formatCode == '*' || (WTF::isASCIIDigit(formatCode) && formatCode != '0')) { // validate codes which ends with '*f' or 'nf' formatCode = inputMask[++i]; if ((i + 1 != maskLength) || formatCodes().find(formatCode) == -1) { isValid = false; break; } hasWildcard = true; } else if (formatCode == '\\') { //skip over the next mask character ++i; ++escapeCharCount; } else { isValid = false; break; } } } if (!isValid) return String(); // calculate the number of characters allowed to be entered by input mask m_numOfCharsAllowedByMask = maskLength; if (escapeCharCount) m_numOfCharsAllowedByMask -= escapeCharCount; if (hasWildcard) { formatCode = inputMask[maskLength - 2]; if (formatCode == '*') m_numOfCharsAllowedByMask = m_data.maxLength(); else { unsigned leftLen = String(&formatCode).toInt(); m_numOfCharsAllowedByMask = leftLen + m_numOfCharsAllowedByMask - 2; } } return inputMask; } bool WMLInputElement::isConformedToInputMask(const String& inputChars) { for (unsigned i = 0; i < inputChars.length(); ++i) if (!isConformedToInputMask(inputChars[i], i + 1, false)) return false; return true; } bool WMLInputElement::isConformedToInputMask(UChar inChar, unsigned inputCharCount, bool isUserInput) { if (m_formatMask.isEmpty()) return true; if (inputCharCount > m_numOfCharsAllowedByMask) return false; unsigned maskIndex = 0; if (isUserInput) { unsigned cursorPosition = 0; if (renderer()) cursorPosition = toRenderTextControl(renderer())->selectionStart(); else cursorPosition = m_data.cachedSelectionStart(); maskIndex = cursorPositionToMaskIndex(cursorPosition); } else maskIndex = cursorPositionToMaskIndex(inputCharCount - 1); bool ok = true; UChar mask = m_formatMask[maskIndex]; // match the inputed character with input mask switch (mask) { case 'A': ok = !WTF::isASCIIDigit(inChar) && !WTF::isASCIILower(inChar) && WTF::isASCIIPrintable(inChar); break; case 'a': ok = !WTF::isASCIIDigit(inChar) && !WTF::isASCIIUpper(inChar) && WTF::isASCIIPrintable(inChar); break; case 'N': ok = WTF::isASCIIDigit(inChar); break; case 'n': ok = !WTF::isASCIIAlpha(inChar) && WTF::isASCIIPrintable(inChar); break; case 'X': ok = !WTF::isASCIILower(inChar) && WTF::isASCIIPrintable(inChar); break; case 'x': ok = !WTF::isASCIIUpper(inChar) && WTF::isASCIIPrintable(inChar); break; case 'M': ok = WTF::isASCIIPrintable(inChar); break; case 'm': ok = WTF::isASCIIPrintable(inChar); break; default: ok = (mask == inChar); break; } return ok; } unsigned WMLInputElement::cursorPositionToMaskIndex(unsigned cursorPosition) { UChar mask; int index = -1; do { mask = m_formatMask[++index]; if (mask == '\\') ++index; else if (mask == '*' || (WTF::isASCIIDigit(mask) && mask != '0')) { index = m_formatMask.length() - 1; break; } } while (cursorPosition--); return index; } } #endif