Bug 1712255 - Defer SetMaxLength in SetValueFromSetRangeText r=masayuki
Capping selection range in SetValue early makes the subsequent SetSelectionRange call unable to detect actual selection range change. This patch defers it so that select events can be consistently fired. Differential Revision: https://phabricator.services.mozilla.com/D115729
This commit is contained in:
@@ -5441,6 +5441,7 @@ void HTMLInputElement::GetValueFromSetRangeText(nsAString& aValue) {
|
||||
|
||||
nsresult HTMLInputElement::SetValueFromSetRangeText(const nsAString& aValue) {
|
||||
return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI,
|
||||
ValueSetterOption::BySetRangeTextAPI,
|
||||
ValueSetterOption::SetValueChanged});
|
||||
}
|
||||
|
||||
|
||||
@@ -660,6 +660,7 @@ void HTMLTextAreaElement::GetValueFromSetRangeText(nsAString& aValue) {
|
||||
nsresult HTMLTextAreaElement::SetValueFromSetRangeText(
|
||||
const nsAString& aValue) {
|
||||
return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI,
|
||||
ValueSetterOption::BySetRangeTextAPI,
|
||||
ValueSetterOption::SetValueChanged});
|
||||
}
|
||||
|
||||
|
||||
@@ -2338,7 +2338,10 @@ void TextControlState::SetRangeText(const nsAString& aReplacement,
|
||||
}
|
||||
|
||||
SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv);
|
||||
// The instance may have already been deleted here.
|
||||
if (IsSelectionCached()) {
|
||||
// SetValueFromSetRangeText skipped SetMaxLength, set it here properly
|
||||
GetSelectionProperties().SetMaxLength(value.Length());
|
||||
}
|
||||
}
|
||||
|
||||
void TextControlState::DestroyEditor() {
|
||||
@@ -2902,7 +2905,13 @@ bool TextControlState::SetValueWithoutTextEditor(
|
||||
aHandlingSetValue.ValueSetterOptionsRef()));
|
||||
|
||||
SelectionProperties& props = GetSelectionProperties();
|
||||
props.SetMaxLength(aHandlingSetValue.GetSettingValue().Length());
|
||||
// Setting a max length and thus capping selection range early prevents
|
||||
// selection change detection in setRangeText. Temporarily disable
|
||||
// capping here with UINT32_MAX, and set it later in ::SetRangeText().
|
||||
props.SetMaxLength(aHandlingSetValue.ValueSetterOptionsRef().contains(
|
||||
ValueSetterOption::BySetRangeTextAPI)
|
||||
? UINT32_MAX
|
||||
: aHandlingSetValue.GetSettingValue().Length());
|
||||
if (aHandlingSetValue.ValueSetterOptionsRef().contains(
|
||||
ValueSetterOption::MoveCursorToEndIfValueChanged)) {
|
||||
props.SetStart(aHandlingSetValue.GetSettingValue().Length());
|
||||
|
||||
@@ -183,6 +183,9 @@ class TextControlState final : public SupportsWeakPtr {
|
||||
// The value is changed by changing value attribute of the element or
|
||||
// something like setRangeText().
|
||||
ByContentAPI,
|
||||
// The value is changed by setRangeText(). Intended to prevent silent
|
||||
// selection range change.
|
||||
BySetRangeTextAPI,
|
||||
// Whether SetValueChanged should be called as a result of this value
|
||||
// change.
|
||||
SetValueChanged,
|
||||
|
||||
@@ -32,6 +32,12 @@
|
||||
input,
|
||||
]
|
||||
|
||||
function untilEvent(element, eventName) {
|
||||
return new Promise((resolve) => {
|
||||
element.addEventListener(eventName, resolve, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
elements.forEach(function(element) {
|
||||
test(function() {
|
||||
element.value = "foobar";
|
||||
@@ -105,16 +111,16 @@
|
||||
});
|
||||
}, element.id + " setRangeText without argument throws a type error");
|
||||
|
||||
async_test(function() {
|
||||
promise_test(async (t) => {
|
||||
// At this point there are already "select" events queued up on
|
||||
// "element". Give them time to fire; otherwise we can get spurious
|
||||
// passes.
|
||||
//
|
||||
// This is unfortunately racy in that we might _still_ get spurious
|
||||
// passes. I'm not sure how best to handle that.
|
||||
this.step_timeout(function() {
|
||||
t.step_timeout(function() {
|
||||
var q = false;
|
||||
element.onselect = this.step_func_done(function(e) {
|
||||
element.onselect = t.step_func_done(function(e) {
|
||||
assert_true(q, "event should be queued");
|
||||
assert_true(e.isTrusted, "event is trusted");
|
||||
assert_true(e.bubbles, "event bubbles");
|
||||
@@ -125,5 +131,25 @@
|
||||
}, 10);
|
||||
}, element.id + " setRangeText fires a select event");
|
||||
|
||||
promise_test(async () => {
|
||||
element.value = "XXXXXXXXXXXXXXXXXXX";
|
||||
const { length } = element.value;
|
||||
element.setSelectionRange(0, length);
|
||||
await untilEvent(element, "select");
|
||||
element.setRangeText("foo", 2, 2);
|
||||
await untilEvent(element, "select");
|
||||
assert_equals(element.selectionStart, 0, ".selectionStart");
|
||||
assert_equals(element.selectionEnd, length + 3, ".selectionEnd");
|
||||
}, element.id + " setRangeText fires a select event when fully selected");
|
||||
|
||||
promise_test(async () => {
|
||||
element.value = "XXXXXXXXXXXXXXXXXXX";
|
||||
element.select();
|
||||
await untilEvent(element, "select");
|
||||
element.setRangeText("foo", 2, 2);
|
||||
await untilEvent(element, "select");
|
||||
assert_equals(element.selectionStart, 0, ".selectionStart");
|
||||
assert_equals(element.selectionEnd, element.value.length, ".selectionEnd");
|
||||
}, element.id + " setRangeText fires a select event after select()");
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<input id="input" value="XXXXXXXXXXXXXXXXXXX" width="200"><br>
|
||||
<textarea id="textarea" width="200">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</textarea>
|
||||
<input id="input" width="200"><br>
|
||||
<textarea id="textarea" width="200"></textarea>
|
||||
|
||||
<script>
|
||||
class SelectionChangeCollector {
|
||||
@@ -40,6 +40,7 @@
|
||||
],
|
||||
async initialize() {
|
||||
for (const collector of this.collectors) {
|
||||
collector.target.value = "XXXXXXXXXXXXXXXXXXX";
|
||||
collector.target.blur();
|
||||
collector.target.setSelectionRange(0, 0);
|
||||
}
|
||||
@@ -175,5 +176,15 @@
|
||||
await data.assert_empty_spin();
|
||||
assert_equals(collector.events.length, 1);
|
||||
}, `Calling select() twice on ${name}`);
|
||||
|
||||
promise_test(async () => {
|
||||
await data.initialize();
|
||||
|
||||
target.select();
|
||||
target.setRangeText("foo", 2, 6);
|
||||
|
||||
await data.assert_empty_spin();
|
||||
assert_equals(collector.events.length, 2);
|
||||
}, `Calling setRangeText() after select() on ${name}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user