Commit edeeccc2 authored by Dominic Battre's avatar Dominic Battre Committed by Commit Bot

Deal with changing input type from password to text

A website may have an <input type="password"> that is dynamically changed to an
<input type="text"> when the user clicks on an eye icon to reveal the password.

This means that the text input field now has the user's password which is
susceptible to learning for autofill and the Android keyboard.

We should track the past state of input field and prevent learning if a field
has ever been a password field.

Bug: 794949
Change-Id: I14023dbf9f2e2449bc5155b4432cdb73c7bcc223
Reviewed-on: https://chromium-review.googlesource.com/827075Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Reviewed-by: default avatarKeishi Hattori <keishi@chromium.org>
Reviewed-by: default avatarChangwan Ryu <changwan@chromium.org>
Commit-Queue: Dominic Battré <battre@chromium.org>
Cr-Commit-Position: refs/heads/master@{#524684}
parent 786f7bd0
......@@ -481,6 +481,7 @@ android_library("content_javatests") {
"javatests/src/org/chromium/content/browser/input/ImeLollipopTest.java",
"javatests/src/org/chromium/content/browser/input/ImeTest.java",
"javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java",
"javatests/src/org/chromium/content/browser/input/ImePasswordTest.java",
"javatests/src/org/chromium/content/browser/input/ImeTestUtils.java",
"javatests/src/org/chromium/content/browser/input/InputDialogContainerTest.java",
"javatests/src/org/chromium/content/browser/input/SelectPopupTest.java",
......
......@@ -130,6 +130,11 @@ public class ImeUtils {
outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
}
if ((inputFlags & WebTextInputFlags.HAS_BEEN_PASSWORD_FIELD) != 0) {
outAttrs.inputType =
InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
}
outAttrs.initialSelStart = initialSelStart;
outAttrs.initialSelEnd = initialSelEnd;
}
......
......@@ -48,14 +48,15 @@ class ImeActivityTestRule extends ContentShellActivityTestRule {
private ImeAdapter mImeAdapter;
static final String INPUT_FORM_HTML = "content/test/data/android/input/input_forms.html";
static final String PASSWORD_FORM_HTML = "content/test/data/android/input/password_form.html";
private ContentViewCore mContentViewCore;
private SelectionPopupController mSelectionPopupController;
private TestCallbackHelperContainer mCallbackContainer;
private TestInputMethodManagerWrapper mInputMethodManagerWrapper;
public void setUp() throws Exception {
launchContentShellWithUrlSync(INPUT_FORM_HTML);
public void setUpForUrl(String url) throws Exception {
launchContentShellWithUrlSync(url);
mContentViewCore = getContentViewCore();
mSelectionPopupController = mContentViewCore.getSelectionPopupControllerForTesting();
mInputMethodManagerWrapper = new TestInputMethodManagerWrapper(mContentViewCore) {
......
......@@ -38,7 +38,7 @@ public class ImeLollipopTest {
@Before
public void setUp() throws Exception {
mRule.setUp();
mRule.setUpForUrl(ImeActivityTestRule.INPUT_FORM_HTML);
}
@Test
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser.input;
import android.support.test.filters.MediumTest;
import android.text.InputType;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.content.browser.test.ContentJUnit4ClassRunner;
import org.chromium.content.browser.test.util.JavaScriptUtils;
/**
* IME (input method editor) and text input tests for password fields.
*/
@RunWith(ContentJUnit4ClassRunner.class)
@CommandLineFlags.Add({"expose-internals-for-testing"})
public class ImePasswordTest {
@Rule
public ImeActivityTestRule mRule = new ImeActivityTestRule();
@Before
public void setUp() throws Exception {
mRule.setUpForUrl(ImeActivityTestRule.PASSWORD_FORM_HTML);
}
@Test
@MediumTest
@Feature({"TextInput", "Main"})
public void testKeyboardOnPasswordFieldChangingType() throws Throwable {
mRule.focusElement("input_text");
Assert.assertNotEquals(InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD,
mRule.getConnectionFactory().getOutAttrs().inputType
& InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
// <input type="password"> should be considered a password field.
mRule.focusElement("input_password");
Assert.assertEquals(InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD,
mRule.getConnectionFactory().getOutAttrs().inputType
& InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
// Change input_password to type text.
final String code = "document.getElementById(\"input_password\").type = \"text\"";
JavaScriptUtils.executeJavaScriptAndWaitForResult(
mRule.getContentViewCore().getWebContents(), code);
// <input type="password"> that was changed to type="text" should still
// be considered a password field.
Assert.assertEquals(InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD,
mRule.getConnectionFactory().getOutAttrs().inputType
& InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
// Temporarily focus input_text and verify that it is not a password input.
mRule.focusElement("input_text");
Assert.assertNotEquals(InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD,
mRule.getConnectionFactory().getOutAttrs().inputType
& InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
// Return to input_password and verify that it is still considered a
// password input despite having input="text" now.
mRule.focusElement("input_password");
Assert.assertEquals(InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD,
mRule.getConnectionFactory().getOutAttrs().inputType
& InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Assert.assertEquals("\"text\"",
JavaScriptUtils.executeJavaScriptAndWaitForResult(
mRule.getContentViewCore().getWebContents(),
"document.activeElement.type"));
}
}
......@@ -54,7 +54,7 @@ public class ImeTest {
@Before
public void setUp() throws Exception {
mRule.setUp();
mRule.setUpForUrl(ImeActivityTestRule.INPUT_FORM_HTML);
}
@Test
......
......@@ -47,7 +47,7 @@ public class TextSuggestionMenuTest {
@Before
public void setUp() throws Throwable {
mRule.setUp();
mRule.setUpForUrl(ImeActivityTestRule.INPUT_FORM_HTML);
mRule.fullyLoadUrl(URL);
}
......
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<meta name="viewport" content="width=device-width">
</head>
<body>
<form action="about:blank">
<input id="input_text" type="text" size="10"><br>
<input id="input_password" type="password" size="10">
</form>
<script>
var selectionChangeEventLog = "";
var otherEventLog = "";
var mutationObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
addEventLog(mutation.type, mutation.detail);
});
})
var mutationConfig = { attributes: false, childList: false, characterData: true };
function addOtherEventLog(type, detail) {
if (otherEventLog.length > 0) {
otherEventLog += ',';
}
if (detail == null) {
otherEventLog += type;
} else {
otherEventLog += type + '(' + detail + ')';
}
}
function addSelectionChangeEventLog(type, detail) {
if (selectionChangeEventLog.length > 0) {
selectionChangeEventLog += ',';
}
if (detail == null) {
selectionChangeEventLog += type;
} else {
selectionChangeEventLog += type + '(' + detail + ')';
}
}
// selectionchange event is queued, so it races with the other events.
// crbug.com/628964
function getEventLogs() {
if (otherEventLog.length > 0 && selectionChangeEventLog.length > 0)
return otherEventLog + ',' + selectionChangeEventLog;
return otherEventLog + selectionChangeEventLog;
}
function clearEventLogs() {
selectionChangeEventLog = '';
otherEventLog = '';
}
function addEventListener(element, event_name) {
element.addEventListener(event_name, function (e) { addOtherEventLog(event_name, e.data); });
}
function addKeyEventListener(element, event_name) {
element.addEventListener(event_name, function (e) { addOtherEventLog(event_name, e.keyCode); });
}
function addSelectionEventListener(event_name) {
// Note that listeners added to the element are not effective for now.
document.addEventListener(event_name, function (e) { addSelectionChangeEventLog(event_name, e.data); });
}
function registerListenersAndObserver(element) {
addKeyEventListener(element, "keydown");
addKeyEventListener(element, "keypress");
addKeyEventListener(element, "keyup");
addEventListener(element, "compositionstart");
addEventListener(element, "compositionupdate");
addEventListener(element, "compositionend");
addEventListener(element, "beforeedit");
addEventListener(element, "edit");
addEventListener(element, "select");
addEventListener(element, "change");
addEventListener(element, "input");
mutationObserver.observe(element, mutationConfig);
}
var inputText = document.getElementById("input_text");
var contenteditableEvent = document.getElementById("contenteditable_event");
// SelectionEventListener should be outside registerListenersAndObserver() to avoid duplication.
addSelectionEventListener("selectionchange");
registerListenersAndObserver(inputText);
registerListenersAndObserver(contenteditableEvent);
</script>
</body>
</html>
......@@ -1250,6 +1250,11 @@ int InputMethodController::TextInputFlags() const {
}
}
if (HTMLInputElement* input = ToHTMLInputElementOrNull(element)) {
if (input->HasBeenPasswordField())
flags |= kWebTextInputFlagHasBeenPasswordField;
}
return flags;
}
......
......@@ -111,6 +111,7 @@ HTMLInputElement::HTMLInputElement(Document& document, bool created_by_parser)
should_reveal_password_(false),
needs_to_update_view_value_(true),
is_placeholder_visible_(false),
has_been_password_field_(false),
// |m_inputType| is lazily created when constructed by the parser to avoid
// constructing unnecessarily a text inputType and its shadow subtree,
// just to destroy them when the |type| attribute gets set by the parser
......@@ -374,6 +375,8 @@ void HTMLInputElement::InitializeTypeInParsing() {
String default_value = FastGetAttribute(valueAttr);
if (input_type_->GetValueMode() == ValueMode::kValue)
non_attribute_value_ = SanitizeValue(default_value);
has_been_password_field_ |= new_type_name == InputTypeNames::password;
if (input_type_view_->NeedsShadowSubtree()) {
CreateUserAgentShadowRoot();
CreateShadowSubtree();
......@@ -429,6 +432,8 @@ void HTMLInputElement::UpdateType() {
bool placeholder_changed =
input_type_->SupportsPlaceholder() != new_type->SupportsPlaceholder();
has_been_password_field_ |= new_type_name == InputTypeNames::password;
input_type_ = new_type;
input_type_view_ = input_type_->CreateView();
if (input_type_view_->NeedsShadowSubtree()) {
......@@ -943,6 +948,10 @@ bool HTMLInputElement::IsTextField() const {
return input_type_->IsTextField();
}
bool HTMLInputElement::HasBeenPasswordField() const {
return has_been_password_field_;
}
void HTMLInputElement::DispatchChangeEventIfNeeded() {
if (isConnected() && input_type_->ShouldSendChangeEventAfterCheckedChanged())
DispatchChangeEvent();
......
......@@ -105,6 +105,10 @@ class CORE_EXPORT HTMLInputElement
// isRadio, isFile. If you want to check the input type, you may use
// |input->type() == InputTypeNames::image|, etc.
// Returns whether this field is or has ever been a password field so that
// its value can be protected from memorization by autofill or keyboards.
bool HasBeenPasswordField() const;
bool checked() const;
void setChecked(bool, TextFieldEventBehavior = kDispatchNoEvent);
void DispatchChangeEventIfNeeded();
......@@ -421,6 +425,7 @@ class CORE_EXPORT HTMLInputElement
unsigned should_reveal_password_ : 1;
unsigned needs_to_update_view_value_ : 1;
unsigned is_placeholder_visible_ : 1;
unsigned has_been_password_field_ : 1;
Member<InputType> input_type_;
Member<InputTypeView> input_type_view_;
// The ImageLoader must be owned by this element because the loader code
......
......@@ -88,7 +88,10 @@ enum WebTextInputFlags {
kWebTextInputFlagAutocapitalizeWords = 1 << 8,
kWebTextInputFlagAutocapitalizeSentences = 1 << 9,
kWebTextInputFlagHaveNextFocusableElement = 1 << 10,
kWebTextInputFlagHavePreviousFocusableElement = 1 << 11
kWebTextInputFlagHavePreviousFocusableElement = 1 << 11,
// Whether an input field is or has ever been a password. For such an input
// type we don't want autocomplete or a keyboard to memorize the content.
kWebTextInputFlagHasBeenPasswordField = 1 << 12,
};
} // namespace blink
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment