Commit bb27a500 authored by Kent Tamura's avatar Kent Tamura Committed by Commit Bot

Updating defaultValue in a non-dirty TEXTAREA should not move the caret to the end

According to the HTML standard, updating defaultValue should keep
selectionStart/End, and clamp them if the new value is shorter than them.
defaultValue setter doesn't have any explicit selection behavior [1], and
we should apply default selection adjustment algorithm in such case [2].

The new behavior matches to Edge, Firefox, and Safari.

Some test cases in selection-start-end-extra.html were not correct. This CL
fixes them.  Remove some test cases in textarea-selection-preservation.html
because they are covered by WPT.

[1] https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:dom-textarea-defaultvalue-2
[2] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:concept-textarea/input-relevant-value

Bug: 822639
Change-Id: I77db0934bb10774561e947f64b4cb3f88c293679
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1575317
Commit-Queue: Kent Tamura <tkent@chromium.org>
Reviewed-by: default avatarYoshifumi Inoue <yosin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#653487}
parent f6ec25ae
...@@ -108,7 +108,7 @@ void HTMLTextAreaElement::ChildrenChanged(const ChildrenChange& change) { ...@@ -108,7 +108,7 @@ void HTMLTextAreaElement::ChildrenChanged(const ChildrenChange& change) {
if (is_dirty_) if (is_dirty_)
SetInnerEditorValue(value()); SetInnerEditorValue(value());
else else
SetNonDirtyValue(defaultValue()); SetNonDirtyValue(defaultValue(), TextControlSetValueSelection::kClamp);
} }
bool HTMLTextAreaElement::IsPresentationAttribute( bool HTMLTextAreaElement::IsPresentationAttribute(
...@@ -231,7 +231,8 @@ void HTMLTextAreaElement::AppendToFormData(FormData& form_data) { ...@@ -231,7 +231,8 @@ void HTMLTextAreaElement::AppendToFormData(FormData& form_data) {
} }
void HTMLTextAreaElement::ResetImpl() { void HTMLTextAreaElement::ResetImpl() {
SetNonDirtyValue(defaultValue()); SetNonDirtyValue(defaultValue(),
TextControlSetValueSelection::kSetSelectionToEnd);
} }
bool HTMLTextAreaElement::HasCustomFocusLogic() const { bool HTMLTextAreaElement::HasCustomFocusLogic() const {
...@@ -386,9 +387,10 @@ void HTMLTextAreaElement::setValue(const String& value, ...@@ -386,9 +387,10 @@ void HTMLTextAreaElement::setValue(const String& value,
is_dirty_ = true; is_dirty_ = true;
} }
void HTMLTextAreaElement::SetNonDirtyValue(const String& value) { void HTMLTextAreaElement::SetNonDirtyValue(
SetValueCommon(value, TextFieldEventBehavior::kDispatchNoEvent, const String& value,
TextControlSetValueSelection::kSetSelectionToEnd); TextControlSetValueSelection selection) {
SetValueCommon(value, TextFieldEventBehavior::kDispatchNoEvent, selection);
is_dirty_ = false; is_dirty_ = false;
} }
...@@ -411,6 +413,13 @@ void HTMLTextAreaElement::SetValueCommon( ...@@ -411,6 +413,13 @@ void HTMLTextAreaElement::SetValueCommon(
if (normalized_value == value()) if (normalized_value == value())
return; return;
// selectionStart and selectionEnd values can be changed by
// SetInnerEditorValue(). We need to get them before SetInnerEditorValue() to
// clamp them later in a case of kClamp.
const bool is_clamp = selection == TextControlSetValueSelection::kClamp;
const unsigned selection_start = is_clamp ? selectionStart() : 0;
const unsigned selection_end = is_clamp ? selectionEnd() : 0;
if (event_behavior != TextFieldEventBehavior::kDispatchNoEvent) if (event_behavior != TextFieldEventBehavior::kDispatchNoEvent)
SetValueBeforeFirstUserEditIfNotSet(); SetValueBeforeFirstUserEditIfNotSet();
value_ = normalized_value; value_ = normalized_value;
...@@ -429,6 +438,10 @@ void HTMLTextAreaElement::SetValueCommon( ...@@ -429,6 +438,10 @@ void HTMLTextAreaElement::SetValueCommon(
// Set the caret to the end of the text value except for initialize. // Set the caret to the end of the text value except for initialize.
unsigned end_of_string = value_.length(); unsigned end_of_string = value_.length();
SetSelectionRange(end_of_string, end_of_string); SetSelectionRange(end_of_string, end_of_string);
} else if (is_clamp) {
const unsigned end_of_string = value_.length();
SetSelectionRange(std::min(end_of_string, selection_start),
std::min(end_of_string, selection_end));
} }
NotifyFormStateChanged(); NotifyFormStateChanged();
......
...@@ -80,7 +80,7 @@ class CORE_EXPORT HTMLTextAreaElement final : public TextControlElement { ...@@ -80,7 +80,7 @@ class CORE_EXPORT HTMLTextAreaElement final : public TextControlElement {
void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) const; void HandleBeforeTextInsertedEvent(BeforeTextInsertedEvent*) const;
static String SanitizeUserInputValue(const String&, unsigned max_length); static String SanitizeUserInputValue(const String&, unsigned max_length);
void UpdateValue(); void UpdateValue();
void SetNonDirtyValue(const String&); void SetNonDirtyValue(const String&, TextControlSetValueSelection);
void SetValueCommon(const String&, void SetValueCommon(const String&,
TextFieldEventBehavior, TextFieldEventBehavior,
TextControlSetValueSelection); TextControlSetValueSelection);
......
...@@ -50,6 +50,7 @@ enum class TextFieldEventBehavior { ...@@ -50,6 +50,7 @@ enum class TextFieldEventBehavior {
enum class TextControlSetValueSelection { enum class TextControlSetValueSelection {
kSetSelectionToEnd, kSetSelectionToEnd,
kClamp,
kDoNotSet, kDoNotSet,
}; };
......
This is a testharness.js-based test.
PASS Setting defaultValue in a textarea should move the cursor to the end
PASS Setting defaultValue in a textarea with a value should NOT make any difference
PASS Setting textContent in a textarea should move selection{Start,End} to the end
PASS Adding children to a textarea should move selection{Start,End} to the end
PASS Removing children from a textarea should update selection{Start,End}
PASS Setting the same value (with different newlines) in a textarea should NOT update selection{Start,End}
FAIL Removing child nodes in non-dirty textarea should make selection{Start,End} 0 assert_equals: selectionStart after appendChild expected 0 but got 6
PASS Setting value to a shorter string than defaultValue should correct the cursor position
PASS Shortening value by turning the input type into 'url' should correct selection{Start,End}
PASS Shortening value by turning the input type into 'color' and back to 'text' should correct selection{Start,End}
PASS Resetting a value to a shorter string than defaultValue should correct the cursor position
Harness: the test ran to completion.
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
assert_equals(el.selectionEnd, 0); assert_equals(el.selectionEnd, 0);
el.defaultValue = "123"; el.defaultValue = "123";
assert_equals(el.value.length, 3); assert_equals(el.value.length, 3);
assert_equals(el.selectionStart, 3); assert_equals(el.selectionStart, 0);
assert_equals(el.selectionEnd, 3); assert_equals(el.selectionEnd, 0);
}, "Setting defaultValue in a textarea should move the cursor to the end"); }, "Setting defaultValue in a textarea should NOT move the cursor to the end");
test(function() { test(function() {
var el = document.createElement("textarea"); var el = document.createElement("textarea");
...@@ -34,34 +34,34 @@ ...@@ -34,34 +34,34 @@
test(function() { test(function() {
var el = document.createElement("textarea"); var el = document.createElement("textarea");
el.appendChild(document.createTextNode("abcdef")); el.appendChild(document.createTextNode("abcdef"));
assert_equals(el.selectionStart, 6); assert_equals(el.selectionStart, 0);
assert_equals(el.selectionEnd, 6); assert_equals(el.selectionEnd, 0);
el.textContent = "abcdef123456"; el.textContent = "abcdef123456";
assert_equals(el.selectionStart, 12); assert_equals(el.selectionStart, 0);
assert_equals(el.selectionEnd, 12); assert_equals(el.selectionEnd, 0);
}, "Setting textContent in a textarea should move selection{Start,End} to the end"); }, "Setting textContent in a textarea should NOT move selection{Start,End} to the end");
test(function() { test(function() {
var el = document.createElement("textarea"); var el = document.createElement("textarea");
el.appendChild(document.createTextNode("abcdef")); el.appendChild(document.createTextNode("abcdef"));
assert_equals(el.selectionStart, 6); assert_equals(el.selectionStart, 0);
assert_equals(el.selectionEnd, 6); assert_equals(el.selectionEnd, 0);
el.appendChild(document.createTextNode("123456")); el.appendChild(document.createTextNode("123456"));
assert_equals(el.selectionStart, 12); assert_equals(el.selectionStart, 0);
assert_equals(el.selectionEnd, 12); assert_equals(el.selectionEnd, 0);
}, "Adding children to a textarea should move selection{Start,End} to the end"); }, "Adding children to a textarea should NOT move selection{Start,End} to the end");
test(function() { test(function() {
var el = document.createElement("textarea"); var el = document.createElement("textarea");
el.appendChild(document.createTextNode("abcdef")); el.appendChild(document.createTextNode("abcdef"));
el.appendChild(document.createTextNode("123")); el.appendChild(document.createTextNode("123"));
assert_equals(el.selectionStart, 9); assert_equals(el.selectionStart, 0);
assert_equals(el.selectionEnd, 9); assert_equals(el.selectionEnd, 0);
el.removeChild(el.firstChild); el.removeChild(el.firstChild);
assert_equals(el.selectionStart, 3); assert_equals(el.selectionStart, 0);
assert_equals(el.selectionEnd, 3); assert_equals(el.selectionEnd, 0);
}, "Removing children from a textarea should update selection{Start,End}"); }, "Removing children from a textarea should NOT update selection{Start,End}");
test(function() { test(function() {
var el = document.createElement("textarea"); var el = document.createElement("textarea");
...@@ -104,8 +104,8 @@ ...@@ -104,8 +104,8 @@
var el = document.createElement("textarea"); var el = document.createElement("textarea");
el.defaultValue = "123"; el.defaultValue = "123";
assert_equals(el.value.length, 3); assert_equals(el.value.length, 3);
assert_equals(el.selectionStart, 3); el.selectionStart = 3;
assert_equals(el.selectionEnd, 3); el.selectionEnd = 3;
el.value = "12"; el.value = "12";
assert_equals(el.value.length, 2); assert_equals(el.value.length, 2);
assert_equals(el.selectionStart, 2); assert_equals(el.selectionStart, 2);
......
...@@ -27,15 +27,6 @@ PASS ta.selectionEnd is 4 ...@@ -27,15 +27,6 @@ PASS ta.selectionEnd is 4
- reset form - reset form
PASS ta.selectionStart is 7 PASS ta.selectionStart is 7
PASS ta.selectionEnd is 7 PASS ta.selectionEnd is 7
- set new defaultValue
PASS ta.selectionStart is 9
PASS ta.selectionEnd is 9
- set same defaultValue
PASS ta.selectionStart is 2
PASS ta.selectionEnd is 3
- append a text node
PASS ta.selectionStart is 12
PASS ta.selectionEnd is 12
- append a empty text node - append a empty text node
PASS ta.selectionStart is 2 PASS ta.selectionStart is 2
PASS ta.selectionEnd is 3 PASS ta.selectionEnd is 3
......
...@@ -49,22 +49,6 @@ ...@@ -49,22 +49,6 @@
shouldBe('ta.selectionStart', '7'); shouldBe('ta.selectionStart', '7');
shouldBe('ta.selectionEnd', '7'); shouldBe('ta.selectionEnd', '7');
debug("- set new defaultValue");
ta.defaultValue = 'abc123456';
shouldBe('ta.selectionStart', '9');
shouldBe('ta.selectionEnd', '9');
debug("- set same defaultValue");
ta.setSelectionRange(2, 3);
ta.defaultValue = 'abc123456';
shouldBe('ta.selectionStart', '2');
shouldBe('ta.selectionEnd', '3');
debug("- append a text node");
ta.appendChild(document.createTextNode('foo'));
shouldBe('ta.selectionStart', '12');
shouldBe('ta.selectionEnd', '12');
debug("- append a empty text node"); debug("- append a empty text node");
ta.setSelectionRange(2, 3); ta.setSelectionRange(2, 3);
ta.appendChild(document.createTextNode('')); ta.appendChild(document.createTextNode(''));
......
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