[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.
     //