Right now the spec is pretty much silent on how this stuff works, and as a result browsers have all sorts of behavior differences, both internally and across browsers. Some examples:
In Chrome, var x = document.createElement(tag); x.defaultValue = "abc"; alert(x.selectionStart); will alert 0 if tag is "input" but 3 if tag is "textarea".
In Safari, var x = document.createElement("textarea"); x.defaultValue = "abc"; alert(x.selectionStart); alerts 0. So do var x = document.createElement("textarea"); document.body.appendChild(x); x.defaultValue = "abc"; alert(x.selectionStart); and var x = document.createElement("textarea"); x.selectionStart = x.selectionStart; x.defaultValue = "abc"; alert(x.selectionStart);. But var x = document.createElement("textarea"); document.body.appendChild(x); x.selectionStart = x.selectionStart; x.defaultValue = "abc"; alert(x.selectionStart); alerts 3.
In Gecko... there are various inconsistencies, esp between textarea vs input, display:none vs non-display-none, etc. I'm fixing some of this stuff right now, so not sure it's worth going into the details, which are changing.
OK, what does the spec actually say? It says that setting the "value" IDL attribute of an input that is in "value" mode will move the text entry cursor (if there is one!) to the end of the text.
It also says that setting the "value" IDL attribute of textarea should do the same thing.
It says that UAs must "follow platform conventions to determine their initial state" for the text entry cursor and selection.
That's it. There's no indication of how this initial state should get modified when the value or default value changes...
So here is a proposal for specific behavior:
1) The initial state of the text entry cursor is at the beginning of the text control. The web depends on this behavior, looks like; see https://bugzilla.mozilla.org/show_bug.cgi?id=1337392
2) Setting .value moves the text entry cursor to the end.
3) Anything else that modifies a value just ensures that the text entry cursor is at some position in the range [0, newlength], clamping it to end as needed.
This is what I tentatively plan to implement in Gecko.
@cdumez @domenic @travisleithead
Note that I haven't had a chance to test Edge, so maybe it's got sane behavior....
/cc @tkent-google
Anything else that modifies a value just ensures that the text entry cursor is at some position in the range [0, newlength], clamping it to end as needed.
To be clear, this means leaving the text entry cursor alone, unless it would be outside the [0, newlength] range, in which case it gets clamped?
Yes, exactly. Probably the same thing for selection too.
So basically, adjust selectionStart/selectionEnd to min(currentValue,newLength).
One other problem: it's not clear to me what should happen when the control is focused, or maybe "has been focused". In that case, setting defaultValue seems to put the cursor at the end of the value in browsers? Not sure which parts here are interoperable, exactly.
I recently filed related issues:
https://github.com/whatwg/html/issues/2411
https://github.com/whatwg/html/issues/2412
- In Chrome, var x = document.createElement(tag); x.defaultValue = "abc"; alert(x.selectionStart); will alert 0 if tag is "input" but 3 if tag is "textarea".
This behavior is definitely a bug. Chrome doesn't update selection values for input/defaultValue change at all. selectionStart and selectionEnd can be out of range. Oops.
- The initial state of the text entry cursor is at the beginning of the text control. The web depends on this behavior, looks like;
I agree if this is already interoperable.
- Setting .value moves the text entry cursor to the end.
Agree. This is defined and interoperable.
- Anything else that modifies a value just ensures that the text entry cursor is at some position in the range [0, newlength], clamping it to end as needed.
I think in many cases modifying a value sets the cursor at the end of the text. At least updating textarea/@defaultValue sets it to the end in all major browsers.
I agree if this is already interoperable.
It's complicated. In particular there's ok interop for text controls that come from the parser, I think. But if you try to create one from script, you have to do the equivalent of setting .defaultValue, and suddenly implementations are all over the map per above.
Agree. This is defined and interoperable.
Though https://github.com/whatwg/html/issues/2412 is proposing changing it a bit, right?
I think in many cases modifying a value sets the cursor at the end of the text.
Right, setting .value needs to do that per current spec. The question is about value changes that don't set .value but do change the value it returns.
Most obviously, modifying the value via setRangeText or the user typing or whatnot obviously don't move the cursor. It's not clear how .defaultValue sets should behave. What about reset() calls? Direct mutations to the .nodeValue of a child of a textarea? Might be worth testing what UAs do in practice...
At least updating textarea/@defaultValue sets it to the end in all major browsers
That's only sometimes true in Firefox. I'm pretty sure it's false if the textarea is display:none, for example, or not attached to a document...
It's definitely false in Safari, unless I messed something up testing. See my first comment in this issue.
At least updating textarea/@defaultValue sets it to the end in all major browsers
As noted in https://bugzilla.mozilla.org/show_bug.cgi?id=1345293#c0 this is not necessarily true even in Chrome, depending on what "updating" means....
Once this is sorted out, should update the tests in https://hg.mozilla.org/mozilla-central/file/7b19a6386225/testing/web-platform/mozilla/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html and move them to the shared wpt bits.
I've posted https://github.com/whatwg/html/pull/2437 and https://github.com/w3c/web-platform-tests/pull/5147 around the first of these bugs, #2412, as that seems rather separable. But this and #2411 seem worth solving together; #2411 is largely a subset of the larger question posed here about what happens if the underlying value is changed, somehow.
@bzbarsky's plan in https://github.com/whatwg/html/issues/2424#issuecomment-285148162 still seems sound as a way forward, modulo the change in #2437 to not set selection to the end if the value is the same. However the big question for me is how to spec it. We could either have something vague, like "whenever the value changes, run these steps", or we could try to find all the places that update the value. We'll need to do the latter for tests anyway. So far what I've got are:
I guess from this perspective a vague "whenever the value changes" makes more sense.
For textareas, there's also a difference between changing the element's value and its raw value. It seems like we only care about changes to value; e.g. if you set the raw value to "hell\r\no", then set it to "hell\no", that should not change selection. That's the assumption I made in #2437 at least.
I guess from this perspective a vague "whenever the value changes" makes more sense.
You missed a few. Off the top of my head:
You'd think some of these would be covered by your list, but at least Blink has different behavior for foo.innerHTML = "<input value='something'>" and var i = document.createElement("input"); i.setAttribute("value", "something"); foo.appendChild(i);: the former puts the cursor at the start, and the latter puts it at the end...
For textareas, there's also a difference between changing the element's value and its raw value.
Right. I'm not sure how these interact with selection in practice. Is the selection in the raw value or in the value?
Oh, and I think it's obvious that paste/cut should probably not modify selection/cursor stuff in any ways other than the obvious one.... But at this point it would merely sadden me, not surprise me, to discover that we have no interop there either...
You missed a few. Off the top of my head:
Sounds good. I'll try to add those to the test cases.
Right. I'm not sure how these interact with selection in practice. Is the selection in the raw value or in the value?
I imagine it should be on something with normalized line breaks (so value or API value) since you can't select the \r part of \r\n. I guess API value is the best fit there, hmm. But for determining changes either version with normalized line breaks works, see comment thread at https://github.com/whatwg/html/pull/2437#discussion_r106061270
But for determining changes either version with normalized line breaks works
Don't they give different answers for the "did the value change?" question?
In an ideal world, our definition of "value did not change" should probably be such that doing foo.value = foo.value is treated as "value did not change".
Don't they give different answers for the "did the value change?" question?
I can't see how. They are just two different ways of normalizing away line breaks. As far as I can see apiValue1 === apiValue2 iff value1 === value2. An example input where this is not true would be appreciated.
As far as I can see apiValue1 === apiValue2 iff value1 === value2.
No, that's not true. Here's a testcase:
<!DOCTYPE html>
<form action="http://software.hixie.ch/utilities/cgi/test-tools/echo">
<textarea name="one" cols="7" wrap="hard">some text</textarea>
<textarea name="two" cols="7" wrap="hard">some
text</textarea>
<hr>
<script>
var areas = document.querySelectorAll("textarea");
document.writeln("API values match: ", areas[0].value == areas[1].value);
</script>
<hr>
<input type="submit" value="submit me to see the 'value's">
</form>
The API values are different: one is "some text" and one is "some\ntext". The value ... well, the spec doesn't actually define what the value has to be in this case, though it has certain restrictions on it. In practice, in Firefox, both textareas have "some\r\ntext" as the value. Chrome and Safari seem to not support the "cols" attribute at all. But they allow CSS applied to the textarea to affect its value. So in Chrome and Safari, if I throw <style>textarea { font-family: monospace; width: 50px }</style> into the testcase the value becomes "some\r\ntext" for both textareas. Interop, sigh.
Of course the only spec requirement here is that the value not have more than 7 chars in a row without a \r\n between them, and that the name="two" textarea must have a \r\n after the 'e' of "some". So as far as I can tell "some\r\nt\r\ne\r\nx\r\nt" would be a perfectly valid value for the second control, or for the first one. "some te\r\nxt" would be a perfectly valid value for the first control, but not the second....
Of course the other direction works too: if I have two textareas, one with wrap="hard" and one without, they can have the same API value but different value.
I guess if you restrict to a single textarea, such that you can assume a particular value of the wrap attribute, and you assume that the "UA-defined algorithm" in https://html.spec.whatwg.org/multipage/forms.html#textarea-wrapping-transformation step 2 is a function of only the input string (which it's not required to be!), then value1 == value2 would imply that apiValue1 == apiValue2. But the converse would not necessarily hold.
Thanks. I was indeed thinking one text area, but I guess even then it's not so simple. I think we should compare API values then.
Comparing API values makes the most sense to me.
A related issue that comes very close to the above discussion but I don't think we ever explicitly touched on was noted by @bzbarsky in #2770: when setting the value to something smaller than the currently-selected range (e.g. setting the value to "ab" when selectionStart or selectionEnd are 3) the behavior is not interoperable.
Also, dropping a reminder to myself in this thread to file bugs on browsers for https://github.com/whatwg/html/pull/2770 at the same time as filing bugs for however we fix this. I think filing separate bugs will be a bit annoying since anyone fixing the two bugs will end up touching very similar code so we shouldn't make them do it twice.
I've come across a related issue with the input type change algorithm.
Here's a test case:
<!DOCTYPE HTML>
<title>Type change value sanitization interaction with selection</title>
<meta charset=utf-8>
<input type="text" value="lorem ">
<script>
window.addEventListener("load", () => {
const el = document.querySelector("input");
el.setSelectionRange(el.value.length - 1, el.value.length);
console.log(`'${el.value}'`, el.selectionStart, el.selectionEnd);
el.type = "url";
console.log(`'${el.value}'`, el.selectionStart, el.selectionEnd);
});
</script>
When the type is changed from text to url, the url type's value sanitization algorithm is invoked. This strips trailing whitespace, which means that the IDL value is now "lorem". However, per the current spec, nothing happens to the selection because in this case neither the value content attribute, nor the value IDL attribute, are set (steps 1-3 of the input type change algorithm) and step 9 doesn't apply either. Therefore, as the algorithm is defined, we'd end up with a selectionStart of 6 and a selectionEnd of 7, but a value of length 5.
I was looking at this for Servo - Firefox and Chromium both seem to cap the selectionEnd to 5 in practise.
I can file a separate issue if that would be more appropriate?
It seems like the same issue. Last I checked I made a decent amount of progress in related areas but this and #2411 never got resolved :(. I'm not sure when I'll have time to revisit this area; any help would be appreciated.
Since I see this is still giving @jonleighton and other Servo folks headaches, but I'm still short on time, here's a division of labor that could help.
Summarizing the rest of this thread, it looks like the spec will be something like the current spec (updated as of #2437) for setting .value, plus:
Whenever the value (input) or API value (textarea) changes", run these steps:
- Let _newLength_ be the new length of the value in question.
- If the element has a text entry cursor position, and the position is greater than _newLength_, set it to _newLength_.
Does that sound good?
If so, then I am willing to work on that spec change, but only if someone else writes all the tests for me.
In particular, we'll want to cover the cases in https://github.com/whatwg/html/issues/2424#issuecomment-286580627 (including the reset algorithm, mentioned in https://github.com/whatwg/html/issues/3468), plus those mentioned a comment later in https://github.com/whatwg/html/issues/2424#issuecomment-286605947 where possible, plus type change/value sanitization as mentioned in https://github.com/whatwg/html/issues/2424#issuecomment-351793944, plus the difference between value and API value for text areas as demonstrated in https://github.com/whatwg/html/issues/2424#issuecomment-286636083.
Is anyone up for doing that? Would they like me to write the spec patch first?
I might be able to help a bit here, but I'd personally prefer to see the spec patch first.
I've never written tests for a standard so the more context and explicit instructions I have, the better :-) Should all those tests go into selection-start-end.html? A new file? However I think makes sense?
@emanchado (!) I strongly suspect that how you think makes sense will be just fine, but once you submit some you can ask for review on your PR and iterate from there.
Ok, I have started looking into this (see https://github.com/emanchado/servo/commit/181b3756eadfbd8e4deea9dab948157ba6431d3a) but I have a bunch of questions:
setRangeText the right way to modify the text? Setting a new value will destroy the selection, so this is the way I've found (used in other tests, too) that seems to work. I wonder if I should be testing other ways _in addition to_ this one.setSelectionRange (with the same beginning/end) the right way?setSelectionRange in those cases? Or is it still the same?Working directly with the upstream repository would be preferable to upstreaming them from Servo's copy.
Is setRangeText the right way to modify the text?
What we're trying to specify/test here is what happens to the selection bounds when the underlying value of a field changes under various "edge case" scenarios. What happens to the selection bounds when setRangeText is called is already well-defined (ditto for setting value), so I wouldn't expect to see it in the new tests. The edge cases are other things like when invoking the reset algorithm causes a value change, or changing the type attribute of an input causes the value sanitization algorithm to change the field's value.
There are more edge cases listed in the links in @domenic's comment above:
In particular, we'll want to cover the cases in #2424 (comment) (including the reset algorithm, mentioned in #3468), plus those mentioned a comment later in #2424 (comment) where possible, plus type change/value sanitization as mentioned in #2424 (comment), plus the difference between value and API value for text areas as demonstrated in #2424 (comment).
How to set the cursor position, is setSelectionRange (with the same beginning/end) the right way?
I think that's fine. (Alternatively you could set selectionStart and selectionEnd separately.)
How to detect that the browser doesn't support empty selection, and how do I set the cursor position without setSelectionRange in those cases? Or is it still the same?
I don't think you can detect this, or need to. selectionStart is always either the start of the selection, or the text entry cursor position, and selectionEnd is always either the end of the selection, or the text entry cursor position.
Ok, see different approach in the branch selection-and-cursor-processing-input-textarea (commit 852ec69). Sorry for the delay, I've been busy.
It's obviously incomplete, but I seem to be getting better results:
defaultValue) behave differently in Firefox and Chrome.color type switching test fails in both (in different ways, too). I wonder if this has defined behaviour and/or it's important.Comments welcome. If this looks good now, I'll keep going and will try to cover all the mentioned cases.
Most helpful comment
Ok, see different approach in the branch
selection-and-cursor-processing-input-textarea(commit 852ec69). Sorry for the delay, I've been busy.It's obviously incomplete, but I seem to be getting better results:
defaultValue) behave differently in Firefox and Chrome.colortype switching test fails in both (in different ways, too). I wonder if this has defined behaviour and/or it's important.Comments welcome. If this looks good now, I'll keep going and will try to cover all the mentioned cases.