[Autofill] Use ShadowDOM placeholder to preview suggestions.
The first patch is a re-upload of
https://chromium-review.googlesource.com/c/chromium/src/+/646754
The follow-up patches will add some modifcations on how we preview
username and password suggestions.
The suggestions will be in black text, and the password suggestions
should be hidden behind dots.
Bug: 753645
Change-Id: I1d28ea47f443fc40a1cddf2cdef6b1ec86c4491e
Tbr: tkent@chromium.org
Reviewed-on: https://chromium-review.googlesource.com/702056
Commit-Queue: Sebastien Seguin-Gagnon <sebsg@chromium.org>
Reviewed-by: Yoshifumi Inoue <yosin@chromium.org>
Reviewed-by: Roger McFarlane <rogerm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#509961}
diff --git a/chrome/renderer/autofill/form_autofill_browsertest.cc b/chrome/renderer/autofill/form_autofill_browsertest.cc
index 903a2349..07c07e7f 100644
--- a/chrome/renderer/autofill/form_autofill_browsertest.cc
+++ b/chrome/renderer/autofill/form_autofill_browsertest.cc
@@ -596,8 +596,10 @@
// Verify preview selection.
WebInputElement firstname = GetInputElementById("firstname");
+ // Since the suggestion is previewed as a placeholder, there should be no
+ // selected text.
EXPECT_EQ(0, firstname.SelectionStart());
- EXPECT_EQ(19, firstname.SelectionEnd());
+ EXPECT_EQ(0, firstname.SelectionEnd());
}
void TestUnmatchedUnownedForm(const char* html, const char* url_override) {
@@ -1146,8 +1148,10 @@
form.fields[1].is_autofilled = true;
form.fields[2].is_autofilled = true;
PreviewForm(form, input_element);
- EXPECT_EQ(2, input_element.SelectionStart());
- EXPECT_EQ(5, input_element.SelectionEnd());
+ // Since the suggestion is previewed as a placeholder, there should be no
+ // selected text.
+ EXPECT_EQ(0, input_element.SelectionStart());
+ EXPECT_EQ(0, input_element.SelectionEnd());
// Fill the form.
FillForm(form, input_element);
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index 069cc42..c52a2a8 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -1357,16 +1357,18 @@
EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
selected_element, kAliceUsername, kAlicePassword));
CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
- int username_length = strlen(kAliceUsername);
- CheckUsernameSelection(0, username_length);
+ // Since the suggestion is previewed as a placeholder, there should be no
+ // selected text.
+ CheckUsernameSelection(0, 0);
// Try previewing with a password different from the one that was initially
// sent to the renderer.
EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
selected_element, kBobUsername, kCarolPassword));
CheckTextFieldsSuggestedState(kBobUsername, true, kCarolPassword, true);
- username_length = strlen(kBobUsername);
- CheckUsernameSelection(0, username_length);
+ // Since the suggestion is previewed as a placeholder, there should be no
+ // selected text.
+ CheckUsernameSelection(0, 0);
ClearUsernameAndPasswordFields();
}
@@ -1439,8 +1441,9 @@
EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion(
selected_element, kAliceUsername, kAlicePassword));
CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
- int username_length = strlen(kAliceUsername);
- CheckUsernameSelection(3, username_length);
+ // Since the suggestion is previewed as a placeholder, there should be no
+ // selected text.
+ CheckUsernameSelection(0, 0);
}
}
diff --git a/third_party/WebKit/LayoutTests/fast/forms/javascript-cannot-access-suggested-value.html b/third_party/WebKit/LayoutTests/fast/forms/javascript-cannot-access-suggested-value.html
new file mode 100644
index 0000000..d3baba91
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/forms/javascript-cannot-access-suggested-value.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<div id="log"></div>
+
+<form>
+<input id="ccname" name="ccname" autocomplete="cc-name" autofocus="">
+<input id="cardnumber" name="cardnumber" autocomplete="cc-number">
+<input id="cvc" name="cvc" autocomplete="cc-csc">
+<input id="ccexp" name="ccexp" autocomplete="cc-exp">
+<input type="submit">
+</form>
+<button onclick="document.forms[0].classList.toggle('hidden')">Hide form</button>
+<div id="info" style="position:absolute;left:400px"></div>
+<script>
+// Setup the JavaScript function that tries to access to the suggested values.
+function grabField(field) {
+ // Simulate the user attempting to modify the suggested value.
+ field.focus();
+ field.setSelectionRange(0,0);
+ var result = document.execCommand('insertText', false, ' ');
+
+ var val = field.value.trim();
+ field.value='';
+ assert_equals(val, '', 'The JavaScript should not be able to access the suggested values.');
+}
+
+// Test that tries to grab the suggested value on empty fields.
+test(function() {
+ assert_true(window.internals != null, 'This test requires internals object');
+
+ // Set some suggested values.
+ var ccname = document.getElementById('ccname');
+ var cardnumber = document.getElementById('cardnumber');
+ var cvc = document.getElementById('cvc');
+ var ccexp = document.getElementById('ccexp');
+ internals.setSuggestedValue(ccname, 'suggested ccname');
+ internals.setSuggestedValue(cardnumber, 'suggested cardnumber');
+ internals.setSuggestedValue(cvc, 'suggested cvc');
+ internals.setSuggestedValue(ccexp, 'suggested ccexp');
+
+ // Try to grab the suggested values in the fields.
+ grabField(ccname);
+ grabField(cardnumber);
+ grabField(cvc);
+ grabField(ccexp);
+
+}, "JavaScript should not be able to access the suggested values on empty fields.");
+
+// Test that tries to grab the suggested value on a field that contains a value.
+test(function() {
+ assert_true(window.internals != null, 'This test requires internals object');
+
+ // Setup the JavaScript that adds some values in the fields.
+ function addValue(field) {
+ // Add some value in the field.
+ field.value = "Homer";
+ }
+
+ var ccname = document.getElementById('ccname');
+
+ // Add some value in the field.
+ ccname.value = "Homer";
+
+ // Set the suggested value.
+ internals.setSuggestedValue(ccname, 'suggested ccname');
+
+ // Try to grab the suggested values in the field.
+ grabField(ccname);
+
+}, "JavaScript should not be able to access the suggested value on a field that has some value.");
+
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-empty-suggested-value-expected.txt b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-empty-suggested-value-expected.txt
new file mode 100644
index 0000000..ec0f189e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-empty-suggested-value-expected.txt
@@ -0,0 +1,285 @@
+This test verifies that setting an empty suggested value removes/resets the shadow placeholder value.
+
+Input before setting suggested values:
+| <input>
+| id="test"
+| type="text"
+| value="initial value"
+| this.value="initial value"
+| <shadow:root>
+| <div>
+| id="inner-editor"
+| "initial value"
+| <br>
+| "
+"
+| <input>
+| id="inputwithplaceholder"
+| placeholder="initial placeholder"
+| type="text"
+| this.value=""
+| <shadow:root>
+| <div>
+| id="placeholder"
+| pseudo="-webkit-input-placeholder"
+| style="display: block !important; text-overflow: clip;"
+| shadow:pseudoId="-webkit-input-placeholder"
+| "initial placeholder"
+| <div>
+| id="inner-editor"
+| <br>
+| "
+"
+| <textarea>
+| id="textarea"
+| this.value="initial value"
+| "initial value"
+| <shadow:root>
+| <div>
+| id="inner-editor"
+| "initial value"
+| <br>
+| "
+"
+| <textarea>
+| id="textareaWithPlaceholder"
+| placeholder="initial placeholder"
+| this.value=""
+| <shadow:root>
+| <div>
+| id="placeholder"
+| pseudo="-webkit-input-placeholder"
+| style="display: block !important;"
+| shadow:pseudoId="-webkit-input-placeholder"
+| "initial placeholder"
+| <div>
+| id="inner-editor"
+| <br>
+| "
+"
+| <select>
+| id="select"
+| <option>
+| "CA"
+| <shadow:root>
+| "CA"
+| <option>
+| "TX"
+| <shadow:root>
+| "TX"
+| <shadow:root>
+| <content>
+| select="option,optgroup,hr"
+| "input.value: initial value"
+| "internals.suggestedValue(input): "
+| "inputWithPlaceholder.value: "
+| "internals.suggestedValue(inputWithPlaceholder): "
+| "textarea.value: initial value"
+| "internals.suggestedValue(textarea): "
+| "textareaWithPlaceholder.value: "
+| "internals.suggestedValue(textareaWithPlaceholder): "
+| "select.value: "
+| "internals.suggestedValue(select): "
+
+Input after setting suggestedValue:
+| <input>
+| id="test"
+| type="text"
+| value="initial value"
+| this.value="initial value"
+| <shadow:root>
+| <div>
+| id="placeholder"
+| pseudo="-webkit-input-suggested"
+| style="display: none !important;"
+| shadow:pseudoId="-webkit-input-suggested"
+| "suggested value"
+| <div>
+| id="inner-editor"
+| <br>
+| "
+"
+| <input>
+| id="inputwithplaceholder"
+| placeholder="initial placeholder"
+| type="text"
+| this.value=""
+| <shadow:root>
+| <div>
+| id="placeholder"
+| pseudo="-webkit-input-suggested"
+| style="display: block !important; text-overflow: clip;"
+| shadow:pseudoId="-webkit-input-suggested"
+| "suggested value"
+| <div>
+| id="inner-editor"
+| <br>
+| "
+"
+| <textarea>
+| id="textarea"
+| this.value="initial value"
+| "initial value"
+| <shadow:root>
+| <div>
+| id="placeholder"
+| pseudo="-webkit-input-suggested"
+| style="display: none !important;"
+| shadow:pseudoId="-webkit-input-suggested"
+| "suggested value"
+| <div>
+| id="inner-editor"
+| <br>
+| "
+"
+| <textarea>
+| id="textareaWithPlaceholder"
+| placeholder="initial placeholder"
+| this.value=""
+| <shadow:root>
+| <div>
+| id="placeholder"
+| pseudo="-webkit-input-suggested"
+| style="display: block !important;"
+| shadow:pseudoId="-webkit-input-suggested"
+| "suggested value"
+| <div>
+| id="inner-editor"
+| <br>
+| "
+"
+| <select>
+| id="select"
+| <option>
+| "CA"
+| <shadow:root>
+| "CA"
+| <option>
+| "TX"
+| <shadow:root>
+| "TX"
+| <shadow:root>
+| <content>
+| select="option,optgroup,hr"
+| "input.value: initial value"
+| "internals.suggestedValue(input): "
+| "inputWithPlaceholder.value: "
+| "internals.suggestedValue(inputWithPlaceholder): "
+| "textarea.value: initial value"
+| "internals.suggestedValue(textarea): "
+| "textareaWithPlaceholder.value: "
+| "internals.suggestedValue(textareaWithPlaceholder): "
+| "select.value: "
+| "internals.suggestedValue(select): "
+| "input.value: initial value"
+| "internals.suggestedValue(input): suggested value"
+| "inputWithPlaceholder.value: "
+| "internals.suggestedValue(inputWithPlaceholder): suggested value"
+| "textarea.value: initial value"
+| "internals.suggestedValue(textarea): suggested value"
+| "textareaWithPlaceholder.value: "
+| "internals.suggestedValue(textareaWithPlaceholder): suggested value"
+| "select.value: "
+| "internals.suggestedValue(select): TX"
+
+After resetting suggestedValue value:
+| <input>
+| id="test"
+| type="text"
+| value="initial value"
+| this.value="initial value"
+| <shadow:root>
+| <div>
+| id="inner-editor"
+| "initial value"
+| <br>
+| "
+"
+| <input>
+| id="inputwithplaceholder"
+| placeholder="initial placeholder"
+| type="text"
+| this.value=""
+| <shadow:root>
+| <div>
+| id="placeholder"
+| pseudo="-webkit-input-placeholder"
+| style="display: block !important; text-overflow: clip;"
+| shadow:pseudoId="-webkit-input-placeholder"
+| "initial placeholder"
+| <div>
+| id="inner-editor"
+| <br>
+| "
+"
+| <textarea>
+| id="textarea"
+| this.value="initial value"
+| "initial value"
+| <shadow:root>
+| <div>
+| id="inner-editor"
+| "initial value"
+| <br>
+| "
+"
+| <textarea>
+| id="textareaWithPlaceholder"
+| placeholder="initial placeholder"
+| this.value=""
+| <shadow:root>
+| <div>
+| id="placeholder"
+| pseudo="-webkit-input-placeholder"
+| style="display: block !important;"
+| shadow:pseudoId="-webkit-input-placeholder"
+| "initial placeholder"
+| <div>
+| id="inner-editor"
+| <br>
+| "
+"
+| <select>
+| id="select"
+| <option>
+| "CA"
+| <shadow:root>
+| "CA"
+| <option>
+| "TX"
+| <shadow:root>
+| "TX"
+| <shadow:root>
+| <content>
+| select="option,optgroup,hr"
+| "input.value: initial value"
+| "internals.suggestedValue(input): "
+| "inputWithPlaceholder.value: "
+| "internals.suggestedValue(inputWithPlaceholder): "
+| "textarea.value: initial value"
+| "internals.suggestedValue(textarea): "
+| "textareaWithPlaceholder.value: "
+| "internals.suggestedValue(textareaWithPlaceholder): "
+| "select.value: "
+| "internals.suggestedValue(select): "
+| "input.value: initial value"
+| "internals.suggestedValue(input): suggested value"
+| "inputWithPlaceholder.value: "
+| "internals.suggestedValue(inputWithPlaceholder): suggested value"
+| "textarea.value: initial value"
+| "internals.suggestedValue(textarea): suggested value"
+| "textareaWithPlaceholder.value: "
+| "internals.suggestedValue(textareaWithPlaceholder): suggested value"
+| "select.value: "
+| "internals.suggestedValue(select): TX"
+| "input.value: initial value"
+| "internals.suggestedValue(input): "
+| "inputWithPlaceholder.value: "
+| "internals.suggestedValue(inputWithPlaceholder): "
+| "textarea.value: initial value"
+| "internals.suggestedValue(textarea): "
+| "textareaWithPlaceholder.value: "
+| "internals.suggestedValue(textareaWithPlaceholder): "
+| "select.value: "
+| "internals.suggestedValue(select): "
+| "PASS"
diff --git a/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-empty-suggested-value.html b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-empty-suggested-value.html
new file mode 100644
index 0000000..d0451d4e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-empty-suggested-value.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p id="description">This test verifies that setting an empty suggested value removes/resets the shadow placeholder value.</p>
+<pre><input id="test" type="text" value="initial value"><br>
+<input id="inputwithplaceholder" type="text" placeholder="initial placeholder"><br>
+<textarea id="textarea">initial value</textarea><br>
+<textarea id="textareaWithPlaceholder" placeholder="initial placeholder"></textarea><br>
+<select id="select"><option>CA</option><option>TX</option></select></pre>
+<script src="../../resources/dump-as-markup.js"></script>
+<script>
+
+var input = document.getElementById('test');
+var inputWithPlaceholder = document.getElementById('inputwithplaceholder');
+var textarea = document.getElementById('textarea');
+//textarea.value = "initial value"
+var textareaWithPlaceholder = document.getElementById('textareaWithPlaceholder');
+var select = document.getElementById('select');
+select.selectedIndex = -1;
+var result = document.getElementById('result');
+if (!window.internals)
+ testFailed('This test requires internals object');
+else {
+ input.focus();
+ input.selectionStart = input.selectionEnd = 0;
+
+ function addText(text) {
+ input.parentNode.appendChild(document.createTextNode(text));
+ }
+
+ function log() {
+ function addTextResult(value) { addText(value + ': ' + eval(value)); }
+ addTextResult('input.value');
+ addTextResult('internals.suggestedValue(input)');
+ addTextResult('inputWithPlaceholder.value');
+ addTextResult('internals.suggestedValue(inputWithPlaceholder)');
+ addTextResult('textarea.value');
+ addTextResult('internals.suggestedValue(textarea)');
+ addTextResult('textareaWithPlaceholder.value');
+ addTextResult('internals.suggestedValue(textareaWithPlaceholder)');
+ addTextResult('select.value');
+ addTextResult('internals.suggestedValue(select)');
+ }
+
+ function getSelectedValues(select) {
+ var selectedValues = [];
+ for (var i = 0; i < select.options.length; i++) {
+ var option = select.options[i];
+ if (option.selected)
+ selectedValues.push(option.value);
+ }
+ return selectedValues.join(',');
+ }
+
+ Markup.description(document.getElementById('description').textContent)
+
+ log();
+ Markup.dump(input.parentNode, 'Input before setting suggested values');
+
+ // Set some suggested value to all the fields.
+ internals.setSuggestedValue(input, 'suggested value');
+ internals.setSuggestedValue(inputWithPlaceholder, 'suggested value');
+ internals.setSuggestedValue(textarea, 'suggested value');
+ internals.setSuggestedValue(textareaWithPlaceholder, 'suggested value');
+ internals.setSuggestedValue(select, 'TX');
+ log();
+ Markup.dump(input.parentNode, 'Input after setting suggestedValue');
+
+ // Set an empty suggested value to all the fields.
+ internals.setSuggestedValue(input, '');
+ internals.setSuggestedValue(inputWithPlaceholder, '');
+ internals.setSuggestedValue(textarea, '');
+ internals.setSuggestedValue(textareaWithPlaceholder, '');
+ internals.setSuggestedValue(select, '');
+
+ log();
+ var innerTextValue = internals.shadowRoot(input).lastChild.innerText;
+ var innerTextWithPlaceholderValue = internals.shadowRoot(inputWithPlaceholder).lastChild.innerText;
+ var innerTextAreaValue = internals.shadowRoot(textarea).lastChild.innerText;
+ var innerTextAreaWithPlaceholderValue = internals.shadowRoot(textareaWithPlaceholder).lastChild.innerText;
+ var selectedValues = getSelectedValues(select);
+ addText(innerTextValue == 'initial value' &&
+ innerTextWithPlaceholderValue == '' &&
+ innerTextAreaValue == 'initial value' &&
+ innerTextAreaWithPlaceholderValue == '' &&
+ selectedValues == '' ? 'PASS' : 'FAIL');
+ Markup.dump(input.parentNode, 'After resetting suggestedValue value');
+}
+
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-setvalue-expected.txt b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-setvalue-expected.txt
index 8473c61..0040e2420 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-setvalue-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-setvalue-expected.txt
@@ -8,8 +8,13 @@
| this.value="initial value"
| <shadow:root>
| <div>
-| id="inner-editor"
+| id="placeholder"
+| pseudo="-webkit-input-suggested"
+| style="display: none !important;"
+| shadow:pseudoId="-webkit-input-suggested"
| "suggested value"
+| <div>
+| id="inner-editor"
| "input.value: initial value"
| "internals.suggestedValue(input): suggested value"
| "input.selectionStart: 0"
diff --git a/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-setvalue.html b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-setvalue.html
index 811d0dde..29936e7 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-setvalue.html
+++ b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-after-setvalue.html
@@ -62,8 +62,11 @@
select.value = 'CA';
log();
- var innerTextValue = internals.shadowRoot(input).firstChild.innerText;
- var innerTextAreaValue = internals.shadowRoot(textarea).firstChild.innerText;
+ // Make sure the InnerEditor value is set to the "new value". The
+ // InnerEditor element is the second/last child of the shadow
+ // input/textarea.
+ var innerTextValue = internals.shadowRoot(input).lastChild.innerText;
+ var innerTextAreaValue = internals.shadowRoot(textarea).lastChild.innerText;
var selectedValues = getSelectedValues(select);
addText(innerTextValue == 'new value' &&
innerTextAreaValue == 'new value' &&
diff --git a/third_party/WebKit/LayoutTests/fast/forms/suggested-value-expected.txt b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-expected.txt
index ec844c9..013b480 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/suggested-value-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/forms/suggested-value-expected.txt
@@ -8,8 +8,13 @@
| this.value="initial value"
| <shadow:root>
| <div>
-| id="inner-editor"
+| id="placeholder"
+| pseudo="-webkit-input-suggested"
+| style="display: none !important;"
+| shadow:pseudoId="-webkit-input-suggested"
| "suggested value"
+| <div>
+| id="inner-editor"
| <input>
| id="month"
| type="month"
@@ -65,8 +70,13 @@
| this.value="initial value"
| <shadow:root>
| <div>
-| id="inner-editor"
+| id="placeholder"
+| pseudo="-webkit-input-suggested"
+| style="display: none !important;"
+| shadow:pseudoId="-webkit-input-suggested"
| "suggested value"
+| <div>
+| id="inner-editor"
| <select>
| id="select"
| <option>
diff --git a/third_party/WebKit/LayoutTests/fast/forms/text/input-appearance-autocomplete-expected.html b/third_party/WebKit/LayoutTests/fast/forms/text/input-appearance-autocomplete-expected.html
index 56c7f152..18a14ac5 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/text/input-appearance-autocomplete-expected.html
+++ b/third_party/WebKit/LayoutTests/fast/forms/text/input-appearance-autocomplete-expected.html
@@ -1,5 +1,6 @@
<input id="input" value="hello" style="width: 99px" >
<script>
-input.select();
+input.focus();
+input.setSelectionRange(0,0);
internals.setAutofilled(input, true);
</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/fast/forms/text/password-input-suggested-value-appearance-expected.html b/third_party/WebKit/LayoutTests/fast/forms/text/password-input-suggested-value-appearance-expected.html
new file mode 100644
index 0000000..4af5de1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/forms/text/password-input-suggested-value-appearance-expected.html
@@ -0,0 +1,6 @@
+<input id="input" type="password" value="MyPassword" style="width: 99px" >
+<script>
+input.focus();
+input.setSelectionRange(0,0);
+internals.setAutofilled(input, true);
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/fast/forms/text/password-input-suggested-value-appearance.html b/third_party/WebKit/LayoutTests/fast/forms/text/password-input-suggested-value-appearance.html
new file mode 100644
index 0000000..3a490212
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/forms/text/password-input-suggested-value-appearance.html
@@ -0,0 +1,7 @@
+<input id="input" type="password">
+<script>
+input.focus();
+internals.setSuggestedValue(input, 'MyPassword');
+internals.setAutofilled(input, true);
+input.style.width = '99px';
+</script>
diff --git a/third_party/WebKit/LayoutTests/platform/win7/fast/forms/suggested-value-expected.txt b/third_party/WebKit/LayoutTests/platform/win7/fast/forms/suggested-value-expected.txt
index df440ee2..c27e12f 100644
--- a/third_party/WebKit/LayoutTests/platform/win7/fast/forms/suggested-value-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win7/fast/forms/suggested-value-expected.txt
@@ -8,6 +8,12 @@
| this.value="initial value"
| <shadow:root>
| <div>
+| id="placeholder"
+| pseudo="-webkit-input-suggested"
+| style="display: none !important;"
+| shadow:pseudoId="-webkit-input-suggested"
+| "suggested value"
+| <div>
| id="inner-editor"
| "suggested value"
| <input>
@@ -64,9 +70,12 @@
| id="textarea"
| this.value="initial value"
| <shadow:root>
+| id="placeholder"
+| pseudo="-webkit-input-suggestedx"
+| style="display: none !important;"
+| shadow:pseudoId="-webkit-input-suggested"
| <div>
| id="inner-editor"
-| "suggested value"
| <select>
| id="select"
| <option>
diff --git a/third_party/WebKit/Source/core/css/html.css b/third_party/WebKit/Source/core/css/html.css
index 9cbd5817..0542ecb 100644
--- a/third_party/WebKit/Source/core/css/html.css
+++ b/third_party/WebKit/Source/core/css/html.css
@@ -521,6 +521,10 @@
-webkit-text-security: disc !important;
}
+input[type="password" i]::-webkit-input-suggested {
+ -webkit-text-security: disc !important;
+}
+
input[type="hidden" i], input[type="image" i], input[type="file" i] {
-webkit-appearance: initial;
padding: initial;
diff --git a/third_party/WebKit/Source/core/html/forms/HTMLInputElement.cpp b/third_party/WebKit/Source/core/html/forms/HTMLInputElement.cpp
index 309b7d0d..56d185a4 100644
--- a/third_party/WebKit/Source/core/html/forms/HTMLInputElement.cpp
+++ b/third_party/WebKit/Source/core/html/forms/HTMLInputElement.cpp
@@ -1049,15 +1049,11 @@
setValue(value, kDispatchChangeEvent);
}
-const String& HTMLInputElement::SuggestedValue() const {
- return suggested_value_;
-}
-
void HTMLInputElement::SetSuggestedValue(const String& value) {
if (!input_type_->CanSetSuggestedValue())
return;
needs_to_update_view_value_ = true;
- suggested_value_ = SanitizeValue(value);
+ TextControlElement::SetSuggestedValue(SanitizeValue(value));
SetNeedsStyleRecalc(
kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue));
@@ -1101,14 +1097,16 @@
if (!input_type_->CanSetValue(value))
return;
+ // Clear the suggested value. Use the base class version to not trigger a view
+ // update.
+ TextControlElement::SetSuggestedValue(String());
+
EventQueueScope scope;
String sanitized_value = SanitizeValue(value);
bool value_changed = sanitized_value != this->value();
SetLastChangeWasNotUserEdit();
needs_to_update_view_value_ = true;
- // Prevent TextFieldInputType::setValue from using the suggested value.
- suggested_value_ = String();
input_type_->SetValue(sanitized_value, value_changed, event_behavior,
selection);
@@ -1180,7 +1178,9 @@
// File upload controls will never use this.
DCHECK_NE(type(), InputTypeNames::file);
- suggested_value_ = String();
+ // Clear the suggested value. Use the base class version to not trigger a view
+ // update.
+ TextControlElement::SetSuggestedValue(String());
// Renderer and our event handler are responsible for sanitizing values.
DCHECK(value == input_type_->SanitizeUserInputValue(value) ||
@@ -1690,6 +1690,10 @@
return input_type_view_->UpdatePlaceholderText();
}
+String HTMLInputElement::GetPlaceholderValue() const {
+ return !SuggestedValue().IsEmpty() ? SuggestedValue() : StrippedPlaceholder();
+}
+
bool HTMLInputElement::SupportsAutocapitalize() const {
return input_type_->SupportsAutocapitalize();
}
diff --git a/third_party/WebKit/Source/core/html/forms/HTMLInputElement.h b/third_party/WebKit/Source/core/html/forms/HTMLInputElement.h
index 70d7e60c..f9f0a1db 100644
--- a/third_party/WebKit/Source/core/html/forms/HTMLInputElement.h
+++ b/third_party/WebKit/Source/core/html/forms/HTMLInputElement.h
@@ -144,8 +144,7 @@
String LocalizeValue(const String&) const;
- const String& SuggestedValue() const;
- void SetSuggestedValue(const String&);
+ void SetSuggestedValue(const String& value) override;
void SetEditingValue(const String&);
@@ -293,6 +292,8 @@
unsigned SizeOfRadioGroup() const;
+ String GetPlaceholderValue() const final;
+
protected:
HTMLInputElement(Document&, bool created_by_parser);
@@ -367,9 +368,6 @@
bool SupportsPlaceholder() const final;
void UpdatePlaceholderText() final;
bool IsEmptyValue() const final { return InnerEditorValue().IsEmpty(); }
- bool IsEmptySuggestedValue() const final {
- return SuggestedValue().IsEmpty();
- }
void HandleFocusEvent(Element* old_focused_element, WebFocusType) final;
void HandleBlurEvent() final;
void DispatchFocusInEvent(const AtomicString& event_type,
@@ -404,7 +402,6 @@
AtomicString name_;
// The value string in |value| value mode.
String non_attribute_value_;
- String suggested_value_;
int size_;
// https://html.spec.whatwg.org/multipage/forms.html#concept-input-value-dirty-flag
unsigned has_dirty_value_ : 1;
diff --git a/third_party/WebKit/Source/core/html/forms/HTMLTextAreaElement.cpp b/third_party/WebKit/Source/core/html/forms/HTMLTextAreaElement.cpp
index d48d5be..3da329a 100644
--- a/third_party/WebKit/Source/core/html/forms/HTMLTextAreaElement.cpp
+++ b/third_party/WebKit/Source/core/html/forms/HTMLTextAreaElement.cpp
@@ -394,6 +394,10 @@
normalized_value.Replace("\r\n", "\n");
normalized_value.Replace('\r', '\n');
+ // Clear the suggested value. Use the base class version to not trigger a view
+ // update.
+ TextControlElement::SetSuggestedValue(String());
+
// Return early because we don't want to trigger other side effects when the
// value isn't changing. This is interoperable.
if (normalized_value == value())
@@ -411,7 +415,6 @@
SetNeedsStyleRecalc(
kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue));
- suggested_value_ = String();
SetNeedsValidityCheck();
if (IsFinishedParsingChildren() &&
selection == TextControlSetValueSelection::kSetSelectionToEnd) {
@@ -471,18 +474,8 @@
SetNonDirtyValue(value);
}
-String HTMLTextAreaElement::SuggestedValue() const {
- return suggested_value_;
-}
-
void HTMLTextAreaElement::SetSuggestedValue(const String& value) {
- suggested_value_ = value;
-
- if (!value.IsNull())
- SetInnerEditorValue(suggested_value_);
- else
- SetInnerEditorValue(value_);
- UpdatePlaceholderVisibility();
+ TextControlElement::SetSuggestedValue(value);
SetNeedsStyleRecalc(
kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(StyleChangeReason::kControlValue));
@@ -593,7 +586,7 @@
void HTMLTextAreaElement::UpdatePlaceholderText() {
HTMLElement* placeholder = PlaceholderElement();
- const AtomicString& placeholder_text = FastGetAttribute(placeholderAttr);
+ const String placeholder_text = GetPlaceholderValue();
if (placeholder_text.IsEmpty()) {
if (placeholder)
UserAgentShadowRoot()->RemoveChild(placeholder);
@@ -612,6 +605,11 @@
placeholder->setTextContent(placeholder_text);
}
+String HTMLTextAreaElement::GetPlaceholderValue() const {
+ return !SuggestedValue().IsEmpty() ? SuggestedValue()
+ : FastGetAttribute(placeholderAttr);
+}
+
bool HTMLTextAreaElement::IsInteractiveContent() const {
return true;
}
diff --git a/third_party/WebKit/Source/core/html/forms/HTMLTextAreaElement.h b/third_party/WebKit/Source/core/html/forms/HTMLTextAreaElement.h
index 2b05512..b23eeda 100644
--- a/third_party/WebKit/Source/core/html/forms/HTMLTextAreaElement.h
+++ b/third_party/WebKit/Source/core/html/forms/HTMLTextAreaElement.h
@@ -52,8 +52,7 @@
void setDefaultValue(const String&);
int textLength() const { return value().length(); }
- String SuggestedValue() const;
- void SetSuggestedValue(const String&);
+ void SetSuggestedValue(const String& value) override;
// For ValidityState
String validationMessage() const override;
@@ -87,11 +86,9 @@
bool IsPlaceholderVisible() const override { return is_placeholder_visible_; }
void SetPlaceholderVisibility(bool) override;
bool SupportsPlaceholder() const override { return true; }
+ String GetPlaceholderValue() const final;
void UpdatePlaceholderText() override;
bool IsEmptyValue() const override { return value().IsEmpty(); }
- bool IsEmptySuggestedValue() const final {
- return SuggestedValue().IsEmpty();
- }
bool SupportsAutocapitalize() const override { return true; }
const AtomicString& DefaultAutocapitalize() const override;
@@ -147,7 +144,6 @@
mutable String value_;
mutable bool is_dirty_;
unsigned is_placeholder_visible_ : 1;
- String suggested_value_;
};
} // namespace blink
diff --git a/third_party/WebKit/Source/core/html/forms/TextControlElement.cpp b/third_party/WebKit/Source/core/html/forms/TextControlElement.cpp
index e2ad5395..30253c7 100644
--- a/third_party/WebKit/Source/core/html/forms/TextControlElement.cpp
+++ b/third_party/WebKit/Source/core/html/forms/TextControlElement.cpp
@@ -45,6 +45,7 @@
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLBRElement.h"
+#include "core/html/HTMLDivElement.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/html/shadow/ShadowElementNames.h"
#include "core/html_names.h"
@@ -159,8 +160,8 @@
}
bool TextControlElement::PlaceholderShouldBeVisible() const {
- return SupportsPlaceholder() && IsEmptyValue() && IsEmptySuggestedValue() &&
- !IsPlaceholderEmpty();
+ return SupportsPlaceholder() && InnerEditorValue().IsEmpty() &&
+ (!IsPlaceholderEmpty() || !IsEmptySuggestedValue());
}
HTMLElement* TextControlElement::PlaceholderElement() const {
@@ -972,6 +973,39 @@
return "ltr";
}
+// TODO(crbug.com/772433): Create and use a new suggested-value element instead.
+void TextControlElement::SetSuggestedValue(const String& value) {
+ suggested_value_ = value;
+ if (!suggested_value_.IsEmpty() && !InnerEditorValue().IsEmpty()) {
+ // Save the value that is in the editor and set the editor value to an empty
+ // string. This will allow the suggestion placeholder to be shown to the
+ // user.
+ value_before_set_suggested_value_ = InnerEditorValue();
+ SetInnerEditorValue("");
+ } else if (suggested_value_.IsEmpty() &&
+ !value_before_set_suggested_value_.IsEmpty()) {
+ // Reset the value that was in the editor before showing the suggestion.
+ SetInnerEditorValue(value_before_set_suggested_value_);
+ value_before_set_suggested_value_ = "";
+ }
+
+ UpdatePlaceholderText();
+
+ HTMLElement* placeholder = PlaceholderElement();
+ if (!placeholder)
+ return;
+
+ // Change the pseudo-id to set the style for suggested values or reset the
+ // placeholder style depending on if there is a suggested value.
+ placeholder->SetShadowPseudoId(AtomicString(suggested_value_.IsEmpty()
+ ? "-webkit-input-placeholder"
+ : "-webkit-input-suggested"));
+}
+
+const String& TextControlElement::SuggestedValue() const {
+ return suggested_value_;
+}
+
HTMLElement* TextControlElement::InnerEditorElement() const {
return ToHTMLElementOrDie(
UserAgentShadowRoot()->getElementById(ShadowElementNames::InnerEditor()));
diff --git a/third_party/WebKit/Source/core/html/forms/TextControlElement.h b/third_party/WebKit/Source/core/html/forms/TextControlElement.h
index 580f819..544579c8 100644
--- a/third_party/WebKit/Source/core/html/forms/TextControlElement.h
+++ b/third_party/WebKit/Source/core/html/forms/TextControlElement.h
@@ -141,10 +141,14 @@
String DirectionForFormData() const;
+ virtual void SetSuggestedValue(const String& value);
+ const String& SuggestedValue() const;
+
protected:
TextControlElement(const QualifiedName&, Document&);
bool IsPlaceholderEmpty() const;
virtual void UpdatePlaceholderText() = 0;
+ virtual String GetPlaceholderValue() const = 0;
void ParseAttribute(const AttributeModificationParams&) override;
@@ -182,7 +186,7 @@
virtual bool IsEmptyValue() const = 0;
// Returns true if suggested value is empty. Used to check placeholder
// visibility.
- virtual bool IsEmptySuggestedValue() const { return true; }
+ bool IsEmptySuggestedValue() const { return SuggestedValue().IsEmpty(); }
// Called in dispatchFocusEvent(), after placeholder process, before calling
// parent's dispatchFocusEvent().
virtual void HandleFocusEvent(Element* /* oldFocusedNode */, WebFocusType) {}
@@ -202,6 +206,9 @@
unsigned cached_selection_end_;
TextFieldSelectionDirection cached_selection_direction_;
+ String suggested_value_;
+ String value_before_set_suggested_value_;
+
FRIEND_TEST_ALL_PREFIXES(TextControlElementTest, IndexForPosition);
};
diff --git a/third_party/WebKit/Source/core/html/forms/TextFieldInputType.cpp b/third_party/WebKit/Source/core/html/forms/TextFieldInputType.cpp
index 11091f8..b56962b 100644
--- a/third_party/WebKit/Source/core/html/forms/TextFieldInputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/TextFieldInputType.cpp
@@ -460,7 +460,7 @@
if (!SupportsPlaceholder())
return;
HTMLElement* placeholder = GetElement().PlaceholderElement();
- String placeholder_text = GetElement().StrippedPlaceholder();
+ String placeholder_text = GetElement().GetPlaceholderValue();
if (placeholder_text.IsEmpty()) {
if (placeholder)
placeholder->remove(ASSERT_NO_EXCEPTION);
@@ -525,10 +525,10 @@
}
void TextFieldInputType::UpdateView() {
- if (!GetElement().SuggestedValue().IsNull()) {
- GetElement().SetInnerEditorValue(GetElement().SuggestedValue());
- GetElement().UpdatePlaceholderVisibility();
- } else if (GetElement().NeedsToUpdateViewValue()) {
+ // The suggested values are now shown using placeholder elements, so there is
+ // nothing to do here for the suggested values.
+ if (GetElement().SuggestedValue().IsEmpty() &&
+ GetElement().NeedsToUpdateViewValue()) {
// Update the view only if needsToUpdateViewValue is true. It protects
// an unacceptable view value from being overwritten with the DOM value.
//