Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Implement <input> type state switch logic
Also make sure to use ASCII lowercase instead of Unicode lowercase
during comparison.
  • Loading branch information
TimothyGu committed Jan 14, 2020
1 parent c292572 commit 94b40d3
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 25 deletions.
58 changes: 52 additions & 6 deletions lib/jsdom/living/nodes/HTMLInputElement-impl.js
Expand Up @@ -19,8 +19,9 @@ const {
sanitizeValueByType
} = require("../helpers/form-controls");
const {
parseFloatingPointNumber,
asciiCaseInsensitiveMatch,
asciiLowercase,
parseFloatingPointNumber,
splitOnCommas
} = require("../helpers/strings");
const { isDate } = require("../helpers/dates-and-times");
Expand Down Expand Up @@ -95,6 +96,14 @@ function valueAttributeMode(type) {
return "value";
}

function getTypeFromAttribute(typeAttribute) {
if (typeof typeAttribute !== "string") {
return "text";
}
const type = asciiLowercase(typeAttribute);
return inputAllowedTypes.has(type) ? type : "text";
}

class HTMLInputElementImpl extends HTMLElementImpl {
constructor(globalObject, args, privateData) {
super(globalObject, args, privateData);
Expand Down Expand Up @@ -205,7 +214,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
}
}

_attrModified(name) {
_attrModified(name, value, oldVal) {
const wrapper = idlUtils.wrapperForImpl(this);
if (!this._dirtyValue && name === "value") {
this._value = sanitizeValueByType(this, wrapper.defaultValue);
Expand All @@ -223,9 +232,47 @@ class HTMLInputElementImpl extends HTMLElementImpl {
}
}

if (name === "type") {
const prevType = getTypeFromAttribute(oldVal);
const curType = getTypeFromAttribute(value);
// When an input element's type attribute changes state…
if (prevType !== curType) {
const prevValueMode = valueAttributeMode(prevType);
const curValueMode = valueAttributeMode(curType);
if (prevValueMode === "value" && this._value !== "" &&
(curValueMode === "default" || curValueMode === "default/on")) {
this.setAttributeNS(null, "value", this._value);
} else if (prevValueMode !== "value" && curValueMode === "value") {
this._value = this.getAttributeNS(null, "value") || "";
this._dirtyValue = false;
} else if (prevValueMode !== "filename" && curValueMode === "filename") {
this._value = "";
}

this._signalATypeChange();

this._value = sanitizeValueByType(this, this._value);

const previouslySelectable = this._idlMemberApplies("setRangeText", prevType);
const nowSelectable = this._idlMemberApplies("setRangeText", curType);
if (!previouslySelectable && nowSelectable) {
this._selectionStart = 0;
this._selectionEnd = 0;
this._selectionDirection = "none";
}
}
}

super._attrModified.apply(this, arguments);
}

// https://html.spec.whatwg.org/multipage/input.html#signal-a-type-change
_signalATypeChange() {
if (this._checkedness) {
this._removeOtherRadioCheckedness();
}
}

_formReset() {
const wrapper = idlUtils.wrapperForImpl(this);
this._value = sanitizeValueByType(this, wrapper.defaultValue);
Expand Down Expand Up @@ -551,8 +598,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {

get type() {
const typeAttribute = this.getAttributeNS(null, "type");
const type = typeAttribute && typeAttribute.toLowerCase();
return inputAllowedTypes.has(type) ? type : "text";
return getTypeFromAttribute(typeAttribute);
}

set type(type) {
Expand Down Expand Up @@ -884,8 +930,8 @@ class HTMLInputElementImpl extends HTMLElementImpl {
return null;
}

_idlMemberApplies(member) {
return applicableTypesForIDLMember[member].has(this.type);
_idlMemberApplies(member, type = this.type) {
return applicableTypesForIDLMember[member].has(type);
}

_barredFromConstraintValidationSpecialization() {
Expand Down
1 change: 0 additions & 1 deletion test/web-platform-tests/to-run.yaml
Expand Up @@ -620,7 +620,6 @@ minlength.html: [fail, Reflection not implemented correctly]
range-2.html: [fail, step attribute not yet implemented]
range.html: [fail, step attribute and range semantics not yet implemented]
reset.html: [fail, Reset semantics not yet implemented]
type-change-state.html: [fail, type attribute state switching not yet implemented]

---

Expand Down
@@ -1,33 +1,107 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<!-- This test should not be upstreamed, as it tests the same thing as html/dom/reflection-forms.html. -->
<meta charset=utf-8>
<!-- When upstreaming this test, it should be noted that there is significant overlap with html/dom/reflection-forms.html. -->
<title>Input default type</title>
<link
rel="help"
href="https://html.spec.whatwg.org/multipage/input.html#attr-input-type"
/>
<link rel=help href="https://html.spec.whatwg.org/multipage/input.html#attr-input-type">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<input id="no-type" />
<input id="valid-type" type="password" />
<input id="invalid-type" type="foo" />
<input id=no-type>
<input id=valid-type-hidden type=hidden>
<input id=valid-type-text type=text>
<input id=valid-type-search type=search>
<input id=valid-type-tel type=tel>
<input id=valid-type-url type=url>
<input id=valid-type-email type=email>
<input id=valid-type-password type=password>
<input id=valid-type-date type=date>
<input id=valid-type-month type=month>
<input id=valid-type-week type=week>
<input id=valid-type-time type=time>
<input id=valid-type-datetime-local type=datetime-local>
<input id=valid-type-number type=number>
<input id=valid-type-range type=range>
<input id=valid-type-color type=color>
<input id=valid-type-checkbox type=checkbox>
<input id=valid-type-radio type=radio>
<input id=valid-type-file type=file>
<input id=valid-type-submit type=submit>
<input id=valid-type-image type=image>
<input id=valid-type-reset type=reset>
<input id=valid-type-button type=button>
<input id=valid-type-BUTTON type=BUTTON>
<input id=invalid-type-foo type=foo>
<input id=invalid-type-datetime type=datetime>
<input id=invalid-type-hİdden type=hİdden>
<input id=invalid-type-emaİl type=emaİl>
<input id=invalid-type-tİme type=tİme>
<input id=invalid-type-datetİme-local type=datetİme-local>
<input id=invalid-type-radİo type=radİo>
<input id=invalid-type-fİle type=fİle>
<input id=invalid-type-submİt type=submİt>
<input id=invalid-type-İmage type=İmage>
<input id=invalid-type-weeK type=weeK>
<input id=invalid-type-checKbox type=checKbox>

<script>
"use strict";

test(() => {
const input = document.getElementById("no-type");
assert_true(input.type === "text");
}, "No input type defaults to text");
assert_equals(input.type, "text");
}, "No input type defaults to text (HTML)");

test(() => {
const input = document.getElementById("valid-type");
assert_true(input.type === "password");
}, "Sets valid input type");
const input = document.createElement("input");
assert_equals(input.type, "text");
assert_equals(input.getAttributeNS(null, "type"), null);
}, "No input type defaults to text (DOM)");

test(() => {
const input = document.getElementById("invalid-type");
assert_true(input.type === "text");
}, "Sets invalid input types to text");
const validTypeTests = [
"hidden", "text", "search", "tel", "url", "email", "password", "date", "month", "week", "time", "datetime-local",
"number", "range", "color", "checkbox", "radio", "file", "submit", "image", "reset", "button", "BUTTON"
];
for (const type of validTypeTests) {
test(() => {
const input = document.getElementById(`valid-type-${type}`);
assert_equals(input.type, type.toLowerCase());
}, `${type} should be recognized as a valid input type (HTML)`);

test(() => {
const input = document.createElement("input");
input.type = type;
assert_equals(input.type, type.toLowerCase());
assert_equals(input.getAttributeNS(null, "type"), type);
}, `${type} should be recognized as a valid input type (DOM)`);
}

const invalidTypeTests = [
"foo",
"datetime", // datetime used to be a valid type, but not anymore.

// The following use certain Unicode uppercase characters, which when toLowerCase()'d will get converted to certain
// ASCII letters. However, the algorithms in HTML call for ASCII lowercase, so they should not be treated as valid.

// U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
...validTypeTests.filter(t => t.includes("i")).map(t => t.replace(/i/g, "İ")),

// U+212A KELVIN SIGN
...validTypeTests.filter(t => t.includes("k")).map(t => t.replace(/k/g, "K"))
];
for (const type of invalidTypeTests) {
test(() => {
const input = document.getElementById(`invalid-type-${type}`);
assert_equals(input.type, "text");
assert_equals(input.getAttributeNS(null, "type"), type);
}, `Invalid type ${type} is treated as text (HTML)`);

test(() => {
const input = document.createElement("input");
input.type = "hidden";
assert_equals(input.type, "hidden");
input.type = type;
assert_equals(input.type, "text");
assert_equals(input.getAttributeNS(null, "type"), type);
}, `Invalid type ${type} is treated as text (DOM)`);
}
</script>

0 comments on commit 94b40d3

Please sign in to comment.