Commit fa588e67 authored by eric@webkit.org's avatar eric@webkit.org

2009-09-23 Kent Tamura <tkent@chromium.org>

        Reviewed by Darin Adler.

        Tests for maxLength of <textarea>
        https://bugs.webkit.org/show_bug.cgi?id=29292

        * fast/forms/script-tests/textarea-maxlength.js: Added.
        * fast/forms/textarea-maxlength-expected.txt: Added.
        * fast/forms/textarea-maxlength.html: Added.
2009-09-23  Kent Tamura  <tkent@chromium.org>

        Reviewed by Darin Adler.

        - Support for maxLength of <textarea>
        - Move numGraphemeClusters() and numCharactersInGraphemeClusters() from InputElement to String.
        https://bugs.webkit.org/show_bug.cgi?id=29292

        Test: fast/forms/textarea-maxlength.html

        * dom/InputElement.cpp:
        (WebCore::InputElement::sanitizeUserInputValue):
        (WebCore::InputElement::handleBeforeTextInsertedEvent):
        * html/HTMLTextAreaElement.cpp:
        (WebCore::HTMLTextAreaElement::defaultEventHandler):
        (WebCore::HTMLTextAreaElement::handleBeforeTextInsertedEvent):
        (WebCore::HTMLTextAreaElement::sanitizeUserInputValue):
        (WebCore::HTMLTextAreaElement::maxLength):
        (WebCore::HTMLTextAreaElement::setMaxLength):
        * html/HTMLTextAreaElement.h:
        * html/HTMLTextAreaElement.idl:
        * platform/text/PlatformString.h:
        * platform/text/String.cpp:
        (WebCore::String::numGraphemeClusters):
        (WebCore::String::numCharactersInGraphemeClusters):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@48698 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 43250c6f
2009-09-23 Kent Tamura <tkent@chromium.org>
Reviewed by Darin Adler.
Tests for maxLength of <textarea>
https://bugs.webkit.org/show_bug.cgi?id=29292
* fast/forms/script-tests/textarea-maxlength.js: Added.
* fast/forms/textarea-maxlength-expected.txt: Added.
* fast/forms/textarea-maxlength.html: Added.
2009-09-23 Martin Robinson <martin.james.robinson@gmail.com>
Reviewed by Xan Lopez.
......
description('Tests for HTMLTextAreaElement.maxLength behaviors.');
var textArea = document.createElement('textarea');
document.body.appendChild(textArea);
// No maxlength attribute
shouldBe('textArea.maxLength', '0');
// Invalid maxlength attributes
textArea.setAttribute('maxlength', '-3');
shouldBe('textArea.maxLength', '0');
textArea.setAttribute('maxlength', 'xyz');
shouldBe('textArea.maxLength', '0');
// Valid maxlength attributes
textArea.setAttribute('maxlength', '1');
shouldBe('textArea.maxLength', '1');
textArea.setAttribute('maxlength', '256');
shouldBe('textArea.maxLength', '256');
// Set values to .maxLength
textArea.maxLength = 13;
shouldBe('textArea.getAttribute("maxlength")', '"13"');
textArea.maxLength = -1;
shouldBe('textArea.maxLength', '4294967295');
shouldBe('textArea.getAttribute("maxlength")', '"4294967295"');
// maxLength doesn't truncate the default value.
textArea = document.createElement('textarea');
textArea.setAttribute('maxlength', '3');
textArea.innerHTML = 'abcd';
document.body.appendChild(textArea);
shouldBe('textArea.value', '"abcd"');
// maxLength doesn't truncate .value
textArea.maxLength = 3;
textArea.value = 'abcde';
shouldBe('textArea.value', '"abcde"');
// Set up for user-input tests
function createFocusedTextAreaWithMaxLength3() {
if (textArea)
document.body.removeChild(textArea);
textArea = document.createElement('textarea');
textArea.setAttribute('maxlength', '3');
document.body.appendChild(textArea);
textArea.focus();
}
// Insert text of which length is maxLength.
createFocusedTextAreaWithMaxLength3();
document.execCommand('insertText', false, 'abc');
shouldBe('textArea.value', '"abc"');
// Try to add characters to maxLength characters.
createFocusedTextAreaWithMaxLength3();
textArea.value = 'abc';
document.execCommand('insertText', false, 'def');
shouldBe('textArea.value', '"abc"');
// Replace text
createFocusedTextAreaWithMaxLength3();
textArea.value = 'abc';
document.execCommand('selectAll');
document.execCommand('insertText', false, 'def');
shouldBe('textArea.value', '"def"');
// Existing value is longer than maxLength. We can't add text.
createFocusedTextAreaWithMaxLength3();
textArea.value = 'abcdef';
document.execCommand('insertText', false, 'ghi');
shouldBe('textArea.value', '"abcdef"');
// We can delete a character in the longer value.
createFocusedTextAreaWithMaxLength3();
textArea.value = 'abcdef';
document.execCommand('delete');
shouldBe('textArea.value', '"abcde"');
// A linebreak is 1 character.
createFocusedTextAreaWithMaxLength3();
document.execCommand('insertText', false, 'A');
document.execCommand('insertLineBreak');
document.execCommand('insertText', false, 'B');
shouldBe('textArea.value', '"A\\nB"');
// According to the HTML5 specification, maxLength is code-point length.
// However WebKit handles it as grapheme length.
// fancyX should be treated as 1 grapheme.
var fancyX = "x\u0305\u0332";// + String.fromCharCode(0x305) + String.fromCharCode(0x332);
// u10000 is one character consisted of a surrogate pair.
var u10000 = "\ud800\udc00";
// Inserts 5 code-points in UTF-16
createFocusedTextAreaWithMaxLength3();
document.execCommand('insertText', false, 'AB' + fancyX);
shouldBe('textArea.value', '"AB" + fancyX');
shouldBe('textArea.value.length', '5');
createFocusedTextAreaWithMaxLength3();
textArea.value = 'AB' + fancyX;
textArea.setSelectionRange(2, 5); // Select fancyX
document.execCommand('insertText', false, 'CDE');
shouldBe('textArea.value', '"ABC"');
// Inserts 4 code-points in UTF-16
createFocusedTextAreaWithMaxLength3();
document.execCommand('insertText', false, 'AB' + u10000);
shouldBe('textArea.value', '"AB" + u10000');
shouldBe('textArea.value.length', '4');
createFocusedTextAreaWithMaxLength3();
textArea.value = 'AB' + u10000;
textArea.setSelectionRange(2, 4); // Select u10000
document.execCommand('insertText', false, 'CDE');
shouldBe('textArea.value', '"ABC"');
var successfullyParsed = true;
Tests for HTMLTextAreaElement.maxLength behaviors.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS textArea.maxLength is 0
PASS textArea.maxLength is 0
PASS textArea.maxLength is 0
PASS textArea.maxLength is 1
PASS textArea.maxLength is 256
PASS textArea.getAttribute("maxlength") is "13"
PASS textArea.maxLength is 4294967295
PASS textArea.getAttribute("maxlength") is "4294967295"
PASS textArea.value is "abcd"
PASS textArea.value is "abcde"
PASS textArea.value is "abc"
PASS textArea.value is "abc"
PASS textArea.value is "def"
PASS textArea.value is "abcdef"
PASS textArea.value is "abcde"
PASS textArea.value is "A\nB"
PASS textArea.value is "AB" + fancyX
PASS textArea.value.length is 5
PASS textArea.value is "ABC"
PASS textArea.value is "AB" + u10000
PASS textArea.value.length is 4
PASS textArea.value is "ABC"
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<link rel="stylesheet" href="../../fast/js/resources/js-test-style.css">
<script src="../../fast/js/resources/js-test-pre.js"></script>
</head>
<body>
<p id="description"></p>
<div id="console"></div>
<script src="script-tests/textarea-maxlength.js"></script>
<script src="../../fast/js/resources/js-test-post.js"></script>
</body>
</html>
2009-09-23 Kent Tamura <tkent@chromium.org>
Reviewed by Darin Adler.
- Support for maxLength of <textarea>
- Move numGraphemeClusters() and numCharactersInGraphemeClusters() from InputElement to String.
https://bugs.webkit.org/show_bug.cgi?id=29292
Test: fast/forms/textarea-maxlength.html
* dom/InputElement.cpp:
(WebCore::InputElement::sanitizeUserInputValue):
(WebCore::InputElement::handleBeforeTextInsertedEvent):
* html/HTMLTextAreaElement.cpp:
(WebCore::HTMLTextAreaElement::defaultEventHandler):
(WebCore::HTMLTextAreaElement::handleBeforeTextInsertedEvent):
(WebCore::HTMLTextAreaElement::sanitizeUserInputValue):
(WebCore::HTMLTextAreaElement::maxLength):
(WebCore::HTMLTextAreaElement::setMaxLength):
* html/HTMLTextAreaElement.h:
* html/HTMLTextAreaElement.idl:
* platform/text/PlatformString.h:
* platform/text/String.cpp:
(WebCore::String::numGraphemeClusters):
(WebCore::String::numCharactersInGraphemeClusters):
2009-09-23 Martin Robinson <martin.james.robinson@gmail.com>
Reviewed by Xan Lopez.
......@@ -34,7 +34,6 @@
#include "RenderTextControlSingleLine.h"
#include "SelectionController.h"
#include "TextIterator.h"
#include "TextBreakIterator.h"
#if ENABLE(WML)
#include "WMLInputElement.h"
......@@ -159,23 +158,6 @@ void InputElement::setValueFromRenderer(InputElementData& data, InputElement* in
notifyFormStateChanged(element);
}
static int numCharactersInGraphemeClusters(StringImpl* s, int numGraphemeClusters)
{
if (!s)
return 0;
TextBreakIterator* it = characterBreakIterator(s->characters(), s->length());
if (!it)
return 0;
for (int i = 0; i < numGraphemeClusters; ++i) {
if (textBreakNext(it) == TextBreakDone)
return s->length();
}
return textBreakCurrent(it);
}
String InputElement::sanitizeValue(const InputElement* inputElement, const String& proposedValue)
{
return InputElement::sanitizeUserInputValue(inputElement, proposedValue, s_maximumLength);
......@@ -191,36 +173,15 @@ String InputElement::sanitizeUserInputValue(const InputElement* inputElement, co
string.replace('\r', ' ');
string.replace('\n', ' ');
StringImpl* s = string.impl();
int newLength = numCharactersInGraphemeClusters(s, maxLength);
for (int i = 0; i < newLength; ++i) {
const UChar& current = (*s)[i];
unsigned newLength = string.numCharactersInGraphemeClusters(maxLength);
for (unsigned i = 0; i < newLength; ++i) {
const UChar current = string[i];
if (current < ' ' && current != '\t') {
newLength = i;
break;
}
}
if (newLength < static_cast<int>(string.length()))
return string.left(newLength);
return string;
}
static int numGraphemeClusters(StringImpl* s)
{
if (!s)
return 0;
TextBreakIterator* it = characterBreakIterator(s->characters(), s->length());
if (!it)
return 0;
int num = 0;
while (textBreakNext(it) != TextBreakDone)
++num;
return num;
return string.left(newLength);
}
void InputElement::handleBeforeTextInsertedEvent(InputElementData& data, InputElement* inputElement, Element* element, Event* event)
......@@ -231,15 +192,16 @@ void InputElement::handleBeforeTextInsertedEvent(InputElementData& data, InputEl
// We use RenderTextControlSingleLine::text() instead of InputElement::value()
// because they can be mismatched by sanitizeValue() in
// RenderTextControlSingleLine::subtreeHasChanged() in some cases.
int oldLength = numGraphemeClusters(toRenderTextControlSingleLine(element->renderer())->text().impl());
unsigned oldLength = toRenderTextControlSingleLine(element->renderer())->text().numGraphemeClusters();
// selection() may be a pre-edit text.
int selectionLength = numGraphemeClusters(plainText(element->document()->frame()->selection()->selection().toNormalizedRange().get()).impl());
unsigned selectionLength = plainText(element->document()->frame()->selection()->selection().toNormalizedRange().get()).numGraphemeClusters();
ASSERT(oldLength >= selectionLength);
// Selected characters will be removed by the next text event.
int baseLength = oldLength - selectionLength;
int appendableLength = data.maxLength() - baseLength;
unsigned baseLength = oldLength - selectionLength;
unsigned maxLength = static_cast<unsigned>(data.maxLength()); // maxLength() can never be negative.
unsigned appendableLength = maxLength > baseLength ? maxLength - baseLength : 0;
// Truncate the inserted text to avoid violating the maxLength and other constraints.
BeforeTextInsertedEvent* textEvent = static_cast<BeforeTextInsertedEvent*>(event);
......
......@@ -26,6 +26,7 @@
#include "config.h"
#include "HTMLTextAreaElement.h"
#include "BeforeTextInsertedEvent.h"
#include "ChromeClient.h"
#include "CSSValueKeywords.h"
#include "Document.h"
......@@ -35,12 +36,14 @@
#include "FormDataList.h"
#include "Frame.h"
#include "HTMLNames.h"
#include "InputElement.h"
#include "MappedAttribute.h"
#include "Page.h"
#include "RenderStyle.h"
#include "RenderTextControlMultiLine.h"
#include "ScriptEventListener.h"
#include "Text.h"
#include "TextIterator.h"
#include "VisibleSelection.h"
#include <wtf/StdLibExtras.h>
......@@ -270,10 +273,34 @@ void HTMLTextAreaElement::defaultEventHandler(Event* event)
{
if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->isWheelEvent() || event->type() == eventNames().blurEvent))
toRenderTextControlMultiLine(renderer())->forwardEvent(event);
else if (renderer() && event->isBeforeTextInsertedEvent())
handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event));
HTMLFormControlElementWithState::defaultEventHandler(event);
}
void HTMLTextAreaElement::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event) const
{
ASSERT(event);
ASSERT(renderer());
bool ok;
unsigned maxLength = getAttribute(maxlengthAttr).string().toUInt(&ok);
if (!ok)
return;
unsigned currentLength = toRenderTextControl(renderer())->text().numGraphemeClusters();
unsigned selectionLength = plainText(document()->frame()->selection()->selection().toNormalizedRange().get()).numGraphemeClusters();
ASSERT(currentLength >= selectionLength);
unsigned baseLength = currentLength - selectionLength;
unsigned appendableLength = maxLength > baseLength ? maxLength - baseLength : 0;
event->setText(sanitizeUserInputValue(event->text(), appendableLength));
}
String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength)
{
return proposedValue.left(proposedValue.numCharactersInGraphemeClusters(maxLength));
}
void HTMLTextAreaElement::rendererWillBeDestroyed()
{
updateValue();
......@@ -374,6 +401,16 @@ void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
setValue(value);
}
unsigned HTMLTextAreaElement::maxLength() const
{
return getAttribute(maxlengthAttr).string().toUInt();
}
void HTMLTextAreaElement::setMaxLength(unsigned newValue)
{
setAttribute(maxlengthAttr, String::number(newValue));
}
void HTMLTextAreaElement::accessKeyAction(bool)
{
focus();
......
......@@ -28,6 +28,7 @@
namespace WebCore {
class BeforeTextInsertedEvent;
class VisibleSelection;
class HTMLTextAreaElement : public HTMLFormControlElementWithState {
......@@ -78,6 +79,8 @@ public:
String defaultValue() const;
void setDefaultValue(const String&);
int textLength() const { return value().length(); }
unsigned maxLength() const;
void setMaxLength(unsigned);
void rendererWillBeDestroyed();
......@@ -99,6 +102,8 @@ public:
private:
enum WrapMethod { NoWrap, SoftWrap, HardWrap };
void handleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) const;
static String sanitizeUserInputValue(const String&, unsigned maxLength);
void updateValue() const;
void updatePlaceholderVisibility(bool placeholderValueChanged);
virtual void dispatchFocusEvent();
......
......@@ -34,6 +34,7 @@ module html {
attribute long cols;
attribute boolean disabled;
attribute boolean autofocus;
attribute unsigned long maxLength;
attribute [ConvertNullToNullString] DOMString name;
attribute [ConvertNullToNullString, Reflect] DOMString placeholder;
attribute boolean readOnly;
......
......@@ -254,6 +254,14 @@ public:
// Determines the writing direction using the Unicode Bidi Algorithm rules P2 and P3.
WTF::Unicode::Direction defaultWritingDirection() const { return m_impl ? m_impl->defaultWritingDirection() : WTF::Unicode::LeftToRight; }
// Counts the number of grapheme clusters. A surrogate pair or a sequence
// of a non-combining character and following combining characters is
// counted as 1 grapheme cluster.
unsigned numGraphemeClusters() const;
// Returns the number of characters which will be less than or equal to
// the specified grapheme cluster length.
unsigned numCharactersInGraphemeClusters(unsigned) const;
private:
RefPtr<StringImpl> m_impl;
};
......
......@@ -25,6 +25,7 @@
#include "CString.h"
#include "FloatConversion.h"
#include "StringBuffer.h"
#include "TextBreakIterator.h"
#include "TextEncoding.h"
#include <wtf/dtoa.h>
#include <limits>
......@@ -921,6 +922,31 @@ PassRefPtr<SharedBuffer> utf8Buffer(const String& string)
return SharedBuffer::adoptVector(buffer);
}
unsigned String::numGraphemeClusters() const
{
TextBreakIterator* it = characterBreakIterator(characters(), length());
if (!it)
return length();
unsigned num = 0;
while (textBreakNext(it) != TextBreakDone)
++num;
return num;
}
unsigned String::numCharactersInGraphemeClusters(unsigned numGraphemeClusters) const
{
TextBreakIterator* it = characterBreakIterator(characters(), length());
if (!it)
return min(length(), numGraphemeClusters);
for (unsigned i = 0; i < numGraphemeClusters; ++i) {
if (textBreakNext(it) == TextBreakDone)
return length();
}
return textBreakCurrent(it);
}
} // namespace WebCore
#ifndef NDEBUG
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment