Implement WML specific attributes of WMLInputElement.
authorstaikos@webkit.org <staikos@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Feb 2009 19:29:04 +0000 (19:29 +0000)
committerstaikos@webkit.org <staikos@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 2 Feb 2009 19:29:04 +0000 (19:29 +0000)
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@40482 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/wml/input-format-expected.txt [new file with mode: 0644]
LayoutTests/wml/input-format.html [new file with mode: 0644]
LayoutTests/wml/resources/input-format.js [new file with mode: 0644]
WebCore/ChangeLog
WebCore/wml/WMLInputElement.cpp
WebCore/wml/WMLInputElement.h

index 2019def..0b3a52a 100644 (file)
@@ -1,3 +1,16 @@
+2009-02-02  Yichao Yin  <yichao.yin@torchmobile.com.cn>
+
+        Reviewed by Niko Zimmermann.
+
+        Tests for WML Input
+
+        * wml/input-format-expected.txt: Added.
+        * wml/input-format.html: Added.
+        * wml/resources/input-format.js: Added.
+        (setupTestDocument):
+        (prepareTest):
+        (executeTest):
+
 2009-01-29  Scott Violet  <sky@google.com>
 
         Reviewed by Darin Adler.
diff --git a/LayoutTests/wml/input-format-expected.txt b/LayoutTests/wml/input-format-expected.txt
new file mode 100644 (file)
index 0000000..310cd8a
--- /dev/null
@@ -0,0 +1,53 @@
+WML layout tests - using XHTML scripting
+
+
+Tests input format validation
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS pElement1.textContent is "Result: $input1"
+PASS pElement2.textContent is "Result: $input2"
+PASS pElement3.textContent is "Result: $input3"
+PASS pElement4.textContent is "Result: $input4"
+PASS pElement5.textContent is "Result: $input5"
+PASS pElement6.textContent is "Result: $input6"
+PASS pElement7.textContent is "Result: $input7"
+PASS pElement8.textContent is "Result: $input8"
+PASS pElement9.textContent is "Result: $input9"
+PASS pElement10.textContent is "Result: $input10"
+PASS pElement11.textContent is "Result: $input11"
+PASS pElement12.textContent is "Result: $input12"
+PASS pElement1.textContent is "Result: TeSt0-01"
+PASS pElement2.textContent is "Result: TeSt0-02"
+PASS pElement3.textContent is "Result: TeSt-+03"
+PASS pElement4.textContent is "Result: TeSt0-04"
+PASS pElement5.textContent is "Result: TeSt0-05"
+PASS pElement6.textContent is "Result: "
+PASS pElement7.textContent is "Result: "
+PASS pElement8.textContent is "Result: TeSt0-08"
+PASS pElement9.textContent is "Result: TeSt0-09"
+PASS pElement10.textContent is "Result: 0123456789"
+PASS pElement11.textContent is "Result: TeSt0-11"
+PASS pElement12.textContent is "Result: TeSt0-12"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
+--------
+Frame: '<!--framePath //<!--frame0-->-->'
+--------
+Start test
+Result: TeSt0-01
+Result: TeSt0-02
+Result: TeSt-+03
+Result: TeSt0-04
+Result: TeSt0-05
+Result:
+Result:
+Result: TeSt0-08
+Result: TeSt0-09
+Result: 0123456789
+Result: TeSt0-11
+Result: TeSt0-12
diff --git a/LayoutTests/wml/input-format.html b/LayoutTests/wml/input-format.html
new file mode 100644 (file)
index 0000000..d733d3b
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css">
+<script>var relativePathToLayoutTests = "..";</script>
+<script src="resources/WMLTestCase.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+</head>
+<body>
+<h1>WML layout tests - using XHTML scripting</h1>
+<p id="description"></p>
+<div id="console"></div>
+<script src="resources/input-format.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/wml/resources/input-format.js b/LayoutTests/wml/resources/input-format.js
new file mode 100644 (file)
index 0000000..49d9980
--- /dev/null
@@ -0,0 +1,187 @@
+// [Name] input-format.js 
+
+createWMLTestCase("Tests input format validation");
+
+var pElement1;
+var pElement2;
+var pElement3;
+var pElement4;
+var pElement5;
+var pElement6;
+var pElement7;
+var pElement8;
+var pElement9;
+var pElement10;
+var pElement11;
+var pElement12;
+
+function setupTestDocument() {
+    var cardElement = testDocument.documentElement.firstChild;
+
+    var anchorElement = createWMLElement("anchor");
+    anchorElement.appendChild(testDocument.createTextNode("Start test"));
+    cardElement.appendChild(anchorElement);
+
+    var refreshElement = createWMLElement("refresh");
+    anchorElement.appendChild(refreshElement);
+
+    // Test cases for successful matching, the 'value' can display correclty
+    var inputElement1 = createWMLElement("input");
+    inputElement1.setAttribute("name", "input1");
+    inputElement1.setAttribute("value", "TeSt0-01");
+    inputElement1.setAttribute("format", "AaXxMmNn");
+    cardElement.appendChild(inputElement1);
+
+    var inputElement2 = createWMLElement("input");
+    inputElement2.setAttribute("name", "input2");
+    inputElement2.setAttribute("value", "TeSt0-02");
+    inputElement2.setAttribute("format", "AaXxn\-2N");
+    cardElement.appendChild(inputElement2);
+
+    var inputElement3 = createWMLElement("input");
+    inputElement3.setAttribute("name", "input3");
+    inputElement3.setAttribute("value", "TeSt-+03");
+    inputElement3.setAttribute("format", "AaXx\-\+nN");
+    cardElement.appendChild(inputElement3);
+
+    var inputElement4 = createWMLElement("input");
+    inputElement4.setAttribute("name", "input4");
+    inputElement4.setAttribute("value", "TeSt0-04");
+    inputElement4.setAttribute("format", "*m");
+    cardElement.appendChild(inputElement4);
+
+    var inputElement5 = createWMLElement("input");
+    inputElement5.setAttribute("name", "input5");
+    inputElement5.setAttribute("value", "TeSt0-05");
+    inputElement5.setAttribute("format", "Xam*x");
+    cardElement.appendChild(inputElement5);
+
+    // Test cases for failed matching, if failed, the 'value' should be set to empty
+    var inputElement6 = createWMLElement("input");
+    inputElement6.setAttribute("name", "input6");
+    inputElement6.setAttribute("value", "TeSt0-06");
+    inputElement6.setAttribute("format", "MaXxa3n");
+    cardElement.appendChild(inputElement6);
+    var inputElement7 = createWMLElement("input");
+    inputElement7.setAttribute("name", "input7");
+    inputElement7.setAttribute("value", "TeSt0-07");
+    inputElement7.setAttribute("format", "aaxxmmnn");
+    cardElement.appendChild(inputElement7);
+
+    // Test cases for invalid input mask which should be ignored, 
+    // and the 'value' can display correclty
+    var inputElement8 = createWMLElement("input");
+    inputElement8.setAttribute("name", "input8");
+    inputElement8.setAttribute("value", "TeSt0-08");
+    inputElement8.setAttribute("format", "TeSt0008");
+    cardElement.appendChild(inputElement8);
+
+    var inputElement9 = createWMLElement("input");
+    inputElement9.setAttribute("name", "input9");
+    inputElement9.setAttribute("value", "TeSt0-09");
+    inputElement9.setAttribute("format", "MMMMMMMM0n");
+    cardElement.appendChild(inputElement9);
+
+    var inputElement10 = createWMLElement("input");
+    inputElement10.setAttribute("name", "input10");
+    inputElement10.setAttribute("value", "0123456789");
+    inputElement10.setAttribute("format", "10n");
+    cardElement.appendChild(inputElement10);
+
+    var inputElement11 = createWMLElement("input");
+    inputElement11.setAttribute("name", "input11");
+    inputElement11.setAttribute("value", "TeSt0-11");
+    inputElement11.setAttribute("format", "*AMMMMMM");
+    cardElement.appendChild(inputElement11);
+
+    var inputElement12= createWMLElement("input");
+    inputElement12.setAttribute("name", "input12");
+    inputElement12.setAttribute("value", "TeSt0-12");
+    inputElement12.setAttribute("format", "M*a*M");
+    cardElement.appendChild(inputElement12);
+
+    pElement1 = createWMLElement("p");
+    pElement1.textContent = "Result: $input1";
+    cardElement.appendChild(pElement1);
+
+    pElement2 = createWMLElement("p");
+    pElement2.textContent = "Result: $input2";
+    cardElement.appendChild(pElement2);
+
+    pElement3 = createWMLElement("p");
+    pElement3.textContent = "Result: $input3";
+    cardElement.appendChild(pElement3);
+
+    pElement4 = createWMLElement("p");
+    pElement4.textContent = "Result: $input4";
+    cardElement.appendChild(pElement4);
+
+    pElement5 = createWMLElement("p");
+    pElement5.textContent = "Result: $input5";
+    cardElement.appendChild(pElement5);
+
+    pElement6 = createWMLElement("p");
+    pElement6.textContent = "Result: $input6";
+    cardElement.appendChild(pElement6);
+
+    pElement7 = createWMLElement("p");
+    pElement7.textContent = "Result: $input7";
+    cardElement.appendChild(pElement7);
+
+    pElement8 = createWMLElement("p");
+    pElement8.textContent = "Result: $input8";
+    cardElement.appendChild(pElement8);
+
+    pElement9 = createWMLElement("p");
+    pElement9.textContent = "Result: $input9";
+    cardElement.appendChild(pElement9);
+
+    pElement10 = createWMLElement("p");
+    pElement10.textContent = "Result: $input10";
+    cardElement.appendChild(pElement10);
+
+    pElement11 = createWMLElement("p");
+    pElement11.textContent = "Result: $input11";
+    cardElement.appendChild(pElement11);
+
+    pElement12 = createWMLElement("p");
+    pElement12.textContent = "Result: $input12";
+    cardElement.appendChild(pElement12);
+}
+
+function prepareTest() {
+    shouldBeEqualToString("pElement1.textContent", "Result: $input1");
+    shouldBeEqualToString("pElement2.textContent", "Result: $input2");
+    shouldBeEqualToString("pElement3.textContent", "Result: $input3");
+    shouldBeEqualToString("pElement4.textContent", "Result: $input4");
+    shouldBeEqualToString("pElement5.textContent", "Result: $input5");
+    shouldBeEqualToString("pElement6.textContent", "Result: $input6");
+    shouldBeEqualToString("pElement7.textContent", "Result: $input7");
+    shouldBeEqualToString("pElement8.textContent", "Result: $input8");
+    shouldBeEqualToString("pElement9.textContent", "Result: $input9");
+    shouldBeEqualToString("pElement10.textContent", "Result: $input10");
+    shouldBeEqualToString("pElement11.textContent", "Result: $input11");
+    shouldBeEqualToString("pElement12.textContent", "Result: $input12");
+
+    startTest(25, 15);
+}
+
+function executeTest() {
+    shouldBeEqualToString("pElement1.textContent", "Result: TeSt0-01");
+    shouldBeEqualToString("pElement2.textContent", "Result: TeSt0-02");
+    shouldBeEqualToString("pElement3.textContent", "Result: TeSt-+03");
+    shouldBeEqualToString("pElement4.textContent", "Result: TeSt0-04");
+    shouldBeEqualToString("pElement5.textContent", "Result: TeSt0-05");
+    shouldBeEqualToString("pElement6.textContent", "Result: ");
+    shouldBeEqualToString("pElement7.textContent", "Result: ");
+    shouldBeEqualToString("pElement8.textContent", "Result: TeSt0-08");
+    shouldBeEqualToString("pElement9.textContent", "Result: TeSt0-09");
+    shouldBeEqualToString("pElement10.textContent", "Result: 0123456789");
+    shouldBeEqualToString("pElement11.textContent", "Result: TeSt0-11");
+    shouldBeEqualToString("pElement12.textContent", "Result: TeSt0-12");
+
+    completeTest();
+}
+
+var successfullyParsed = true;
index 552aeeb..00ad2e7 100644 (file)
@@ -1,3 +1,22 @@
+2009-02-02  Yichao Yin  <yichao.yin@torchmobile.com.cn>
+
+        Reviewed by Niko Zimmermann.
+
+        Test: wml/input-format.html
+
+        * wml/WMLInputElement.cpp:
+        (WebCore::WMLInputElement::WMLInputElement):
+        (WebCore::formatCodes):
+        (WebCore::WMLInputElement::dispatchBlurEvent):
+        (WebCore::WMLInputElement::parseMappedAttribute):
+        (WebCore::WMLInputElement::attach):
+        (WebCore::WMLInputElement::defaultEventHandler):
+        (WebCore::WMLInputElement::init):
+        (WebCore::WMLInputElement::validateInputMask):
+        (WebCore::WMLInputElement::isConformedToInputMask):
+        (WebCore::WMLInputElement::cursorPositionToMaskIndex):
+        * wml/WMLInputElement.h:
+
 2009-02-02  Brent Fulgham  <bfulgham@webkit.org>
 
         Build fix only, no review.
index 1a53a3f..51882a2 100644 (file)
@@ -23,7 +23,6 @@
 #if ENABLE(WML)
 #include "WMLInputElement.h"
 
-#include "Document.h"
 #include "EventNames.h"
 #include "FormDataList.h"
 #include "Frame.h"
@@ -31,6 +30,9 @@
 #include "KeyboardEvent.h"
 #include "RenderTextControlSingleLine.h"
 #include "TextEvent.h"
+#include "WMLDocument.h"
+#include "WMLNames.h"
+#include "WMLPageState.h"
 
 namespace WebCore {
 
@@ -38,6 +40,8 @@ WMLInputElement::WMLInputElement(const QualifiedName& tagName, Document* doc)
     : WMLFormControlElementWithState(tagName, doc)
     , m_data(this, this)
     , m_isPasswordField(false)
+    , m_isEmptyOk(false)
+    , m_numOfCharsAllowedByMask(0)
 {
 }
 
@@ -47,6 +51,12 @@ WMLInputElement::~WMLInputElement()
         document()->unregisterForDocumentActivationCallbacks(this);
 }
 
+static const AtomicString& formatCodes()
+{
+    DEFINE_STATIC_LOCAL(AtomicString, codes, ("AaNnXxMm"));
+    return codes;
+}
+
 bool WMLInputElement::isKeyboardFocusable(KeyboardEvent*) const
 {
     return WMLFormControlElementWithState::isFocusable();
@@ -65,6 +75,18 @@ void WMLInputElement::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 = name();
+    if (!nameVariable.isEmpty())
+        wmlPageStateForDocument(document())->storeVariable(nameVariable, val); 
+
     InputElement::dispatchBlurEvent(m_data, document());
     WMLElement::dispatchBlurEvent();
 }
@@ -178,12 +200,14 @@ void WMLInputElement::parseMappedAttribute(MappedAttribute* attr)
         InputElement::parseMaxLengthAttribute(m_data, attr);
     else if (attr->name() == HTMLNames::sizeAttr)
         InputElement::parseSizeAttribute(m_data, 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 'format' attribute
-    // FIXME: Handle 'emptyok' attribute
     // FIXME: Handle 'tabindex' attribute
     // FIXME: Handle 'title' attribute
 }
@@ -210,6 +234,13 @@ void WMLInputElement::attach()
     // on the renderer.
     if (renderer())
         renderer()->updateFromElement();
+
+    // FIXME: maybe this is not a good place to do this since it is possible 
+    // to cause unexpected several times initialise of <input> if we force the
+    // node to be reattached. But placing it here can ensure the run-webkit-tests
+    // get expected result,i.e. multiple-times initialise is expected when making
+    // layout test for WMLInputElement 
+    init();
 }  
 
 void WMLInputElement::detach()
@@ -236,8 +267,14 @@ void WMLInputElement::defaultEventHandler(Event* evt)
 {
     bool clickDefaultFormButton = false;
 
-    if (evt->type() == eventNames().textInputEvent && evt->isTextEvent() && static_cast<TextEvent*>(evt)->data() == "\n")
-        clickDefaultFormButton = true;
+    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], static_cast<RenderTextControl*>(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))) {
@@ -322,6 +359,168 @@ void WMLInputElement::didMoveToNewOwnerDocument()
     WMLElement::didMoveToNewOwnerDocument();
 }
 
+void WMLInputElement::init()
+{
+    String nameVariable = name();
+    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 = static_cast<RenderTextControl*>(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
index 306c593..a2c904f 100644 (file)
@@ -87,9 +87,19 @@ public:
     virtual void willMoveToNewOwnerDocument();
     virtual void didMoveToNewOwnerDocument();
 
+    bool isConformedToInputMask(const String&);
+    bool isConformedToInputMask(UChar, unsigned, bool isUserInput = true);
+
 private:
+    void init();
+    String validateInputMask(const String&);
+    unsigned cursorPositionToMaskIndex(unsigned);
+
     InputElementData m_data;
     bool m_isPasswordField;
+    bool m_isEmptyOk;
+    String m_formatMask;
+    unsigned m_numOfCharsAllowedByMask;
 };
 
 }