| /** |
| * 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<const WMLInputElement*>(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<TextEvent*>(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<KeyboardEvent*>(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<KeyboardEvent*>(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<RenderTextControlSingleLine*>(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 |