Skip to content

Commit

Permalink
More consistent applicability checking for <input> attributes
Browse files Browse the repository at this point in the history
With the exception of the removal of "datetime" <input> type state,
these changes do not appear to be observable (or testable).
  • Loading branch information
TimothyGu committed Jan 14, 2020
1 parent b097c0a commit 11664ca
Showing 1 changed file with 91 additions and 70 deletions.
161 changes: 91 additions & 70 deletions lib/jsdom/living/nodes/HTMLInputElement-impl.js
Expand Up @@ -35,50 +35,49 @@ const filesSymbol = Symbol("files");

// https://html.spec.whatwg.org/multipage/input.html#attr-input-type
const inputAllowedTypes = new Set([
"hidden", "text", "search", "tel", "url", "email", "password", "datetime", "date",
"hidden", "text", "search", "tel", "url", "email", "password", "date",
"month", "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio",
"file", "submit", "image", "reset", "button"
]);

const selectAllowedTypes = new Set([
"text", "search", "tel", "url", "password", "email", "date", "month", "week",
"time", "datetime-local", "color", "file", "number"
]);

const variableLengthSelectionAllowedTypes = new Set(["text", "search", "tel", "url", "password"]);

const maxMinStepTypes = new Set(["date", "month", "week", "time", "datetime-local", "number", "range", "datetime"]);

const valueAsDateTypes = new Set(["date", "month", "week", "time"]);
const valueAsNumberTypes = new Set(["datetime", "date", "month", "week", "time", "datetime-local", "number", "range"]);

// https://html.spec.whatwg.org/multipage/input.html#concept-input-apply
const applicableTypesForAttribute = {
max: maxMinStepTypes,
min: maxMinStepTypes,
step: maxMinStepTypes,
pattern: new Set(["text", "search", "tel", "url", "email", "password"]),
readonly: new Set([
"text", "search", "url", "tel", "email", "password", "date", "month", "week", "time", "datetime-local",
"number"
])
};

function allowSelect(type) {
return selectAllowedTypes.has(type.toLowerCase());
}

function allowVariableLengthSelection(type) {
return variableLengthSelectionAllowedTypes.has(type.toLowerCase());
}

function allowValueAsDate(type) {
return valueAsDateTypes.has(type.toLowerCase());
}
const variableLengthSelectionAllowedTypes = new Set(["text", "search", "url", "tel", "password"]);
const numericTypes = new Set(["date", "month", "week", "time", "datetime-local", "number", "range"]);

const applicableTypesForIDLMember = {
valueAsDate: new Set(["date", "month", "week", "time"]),
valueAsNumber: numericTypes,

select: new Set([
"text", "search", "url", "tel", "email", "password", "date", "month", "week",
"time", "datetime-local", "number", "color", "file"
]),
selectionStart: variableLengthSelectionAllowedTypes,
selectionEnd: variableLengthSelectionAllowedTypes,
selectionDirection: variableLengthSelectionAllowedTypes,
setRangeText: variableLengthSelectionAllowedTypes,
setSelectionRange: variableLengthSelectionAllowedTypes,
stepDown: numericTypes,
stepUp: numericTypes
};

function allowValueAsNumber(type) {
return valueAsNumberTypes.has(type.toLowerCase());
}
const lengthPatternSizeTypes = new Set(["text", "search", "url", "tel", "email", "password"]);
const readonlyTypes =
new Set([...lengthPatternSizeTypes, "date", "month", "week", "time", "datetime-local", "number"]);

const applicableTypesForContentAttribute = {
list: new Set(["text", "search", "url", "tel", "email", ...numericTypes, "color"]),
max: numericTypes,
maxlength: lengthPatternSizeTypes,
min: numericTypes,
minlength: lengthPatternSizeTypes,
multiple: new Set(["email", "file"]),
pattern: lengthPatternSizeTypes,
readonly: readonlyTypes,
required: new Set([...readonlyTypes, "checkbox", "radio", "file"]),
step: numericTypes
};

const valueAttributeDefaultMode = new Set(["hidden", "submit", "image", "reset", "button"]);
const valueAttributeDefaultOnMode = new Set(["checkbox", "radio"]);
Expand Down Expand Up @@ -299,7 +298,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
}

get _mutable() {
return !isDisabled(this) && !(this.hasAttributeNS(null, "readonly") && this._attributeApplies("readonly"));
return !isDisabled(this) && !this._hasAttributeAndApplies("readonly");
}

get labels() {
Expand Down Expand Up @@ -387,7 +386,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {

// https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasdate
get valueAsDate() {
if (!allowValueAsDate(this.type)) {
if (!this._idlMemberApplies("valueAsDate")) {
return null;
}

Expand All @@ -402,7 +401,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
}

set valueAsDate(v) {
if (!allowValueAsDate(this.type)) {
if (!this._idlMemberApplies("valueAsDate")) {
throw DOMException.create(this._globalObject, [
"Failed to set the 'valueAsDate' property on 'HTMLInputElement': This input element does not support Date " +
"values.",
Expand All @@ -424,7 +423,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {

// https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasnumber
get valueAsNumber() {
if (!allowValueAsNumber(this.type)) {
if (!this._idlMemberApplies("valueAsNumber")) {
return NaN;
}

Expand All @@ -436,7 +435,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
throw new TypeError("Failed to set infinite value as Number");
}

if (!allowValueAsNumber(this.type)) {
if (!this._idlMemberApplies("valueAsNumber")) {
throw DOMException.create(this._globalObject, [
"Failed to set the 'valueAsNumber' property on 'HTMLInputElement': This input element does not support " +
"Number values.",
Expand All @@ -449,9 +448,10 @@ class HTMLInputElementImpl extends HTMLElementImpl {

// https://html.spec.whatwg.org/multipage/input.html#dom-input-stepup
_stepUpdate(n, isUp) {
if (!allowValueAsNumber(this.type)) {
const methodName = isUp ? "stepUp" : "stepDown";
if (!this._idlMemberApplies(methodName)) {
throw DOMException.create(this._globalObject, [
`Failed to invoke '${isUp ? "stepUp" : "stepDown"}' method on 'HTMLInputElement': ` +
`Failed to invoke '${methodName}' method on 'HTMLInputElement': ` +
"This input element does not support Number values.",
"InvalidStateError"
]);
Expand All @@ -460,7 +460,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
const allowedValueStep = this._allowedValueStep;
if (allowedValueStep === null) {
throw DOMException.create(this._globalObject, [
`Failed to invoke '${isUp ? "stepUp" : "stepDown"}' method on 'HTMLInputElement': ` +
`Failed to invoke '${methodName}' method on 'HTMLInputElement': ` +
"This input element does not support value step.",
"InvalidStateError"
]);
Expand Down Expand Up @@ -564,7 +564,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
}

select() {
if (!allowSelect(this.type)) {
if (!this._idlMemberApplies("select")) {
return;
}

Expand All @@ -575,55 +575,55 @@ class HTMLInputElementImpl extends HTMLElementImpl {
}

get selectionStart() {
if (!allowVariableLengthSelection(this.type)) {
if (!this._idlMemberApplies("selectionStart")) {
return null;
}

return this._selectionStart;
}

set selectionStart(start) {
if (!allowVariableLengthSelection(this.type)) {
if (!this._idlMemberApplies("selectionStart")) {
throw DOMException.create(this._globalObject, ["The object is in an invalid state.", "InvalidStateError"]);
}

this.setSelectionRange(start, Math.max(start, this._selectionEnd), this._selectionDirection);
}

get selectionEnd() {
if (!allowVariableLengthSelection(this.type)) {
if (!this._idlMemberApplies("selectionEnd")) {
return null;
}

return this._selectionEnd;
}

set selectionEnd(end) {
if (!allowVariableLengthSelection(this.type)) {
if (!this._idlMemberApplies("selectionEnd")) {
throw DOMException.create(this._globalObject, ["The object is in an invalid state.", "InvalidStateError"]);
}

this.setSelectionRange(this._selectionStart, end, this._selectionDirection);
}

get selectionDirection() {
if (!allowVariableLengthSelection(this.type)) {
if (!this._idlMemberApplies("selectionDirection")) {
return null;
}

return this._selectionDirection;
}

set selectionDirection(dir) {
if (!allowVariableLengthSelection(this.type)) {
if (!this._idlMemberApplies("selectionDirection")) {
throw DOMException.create(this._globalObject, ["The object is in an invalid state.", "InvalidStateError"]);
}

this.setSelectionRange(this._selectionStart, this._selectionEnd, dir);
}

setSelectionRange(start, end, dir) {
if (!allowVariableLengthSelection(this.type)) {
if (!this._idlMemberApplies("setSelectionRange")) {
throw DOMException.create(this._globalObject, ["The object is in an invalid state.", "InvalidStateError"]);
}

Expand All @@ -634,7 +634,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
}

setRangeText(repl, start, end, selectionMode = "preserve") {
if (!allowVariableLengthSelection(this.type)) {
if (!this._idlMemberApplies("setRangeText")) {
throw DOMException.create(this._globalObject, ["The object is in an invalid state.", "InvalidStateError"]);
}

Expand Down Expand Up @@ -683,7 +683,11 @@ class HTMLInputElementImpl extends HTMLElementImpl {

// https://html.spec.whatwg.org/multipage/input.html#the-list-attribute
get list() {
const id = this.getAttributeNS(null, "list");
const id = this._getAttributeIfApplies("list");
if (!id) {
return null;
}

const el = this.getRootNode({}).getElementById(id);

if (el && el.localName === "datalist") {
Expand All @@ -700,6 +704,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
this.setAttributeNS(null, "maxlength", String(value));
}

// Reflected IDL attribute does not care about whether the content attribute applies.
get maxLength() {
if (!this.hasAttributeNS(null, "maxlength")) {
return 524288; // stole this from chrome
Expand Down Expand Up @@ -746,7 +751,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
// https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes
get _minimum() {
let min = this._defaultMinimum;
const attr = this.getAttributeNS(null, "min");
const attr = this._getAttributeIfApplies("min");
if (attr !== null && this._convertStringToNumber !== undefined) {
const parsed = this._convertStringToNumber(attr);
if (!isNaN(parsed)) {
Expand All @@ -758,7 +763,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {

get _maximum() {
let max = this._defaultMaximum;
const attr = this.getAttributeNS(null, "max");
const attr = this._getAttributeIfApplies("max");
if (attr !== null && this._convertStringToNumber !== undefined) {
const parsed = this._convertStringToNumber(attr);
if (!isNaN(parsed)) {
Expand Down Expand Up @@ -791,14 +796,14 @@ class HTMLInputElementImpl extends HTMLElementImpl {

// https://html.spec.whatwg.org/multipage/input.html#concept-input-step
get _allowedValueStep() {
const attr = this.getAttributeNS(null, "step");
const attr = this._getAttributeIfApplies("step");
if (attr === null) {
return this._defaultStep * this._stepScaleFactor;
}
if (asciiCaseInsensitiveMatch(attr, "any")) {
return null;
}
const parsedStep = parseFloatingPointNumber(this.getAttributeNS(null, "step"));
const parsedStep = parseFloatingPointNumber(attr);
if (isNaN(parsedStep) || parsedStep <= 0) {
return this._defaultStep * this._stepScaleFactor;
}
Expand Down Expand Up @@ -831,7 +836,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {

// https://html.spec.whatwg.org/multipage/input.html#concept-input-min-zero
get _stepBase() {
if (this.hasAttributeNS(null, "min")) {
if (this._hasAttributeAndApplies("min")) {
const min = this._convertStringToNumber(this.getAttributeNS(null, "min"));
if (!isNaN(min)) {
return min;
Expand All @@ -855,8 +860,25 @@ class HTMLInputElementImpl extends HTMLElementImpl {
return 0;
}

_attributeApplies(attribute) {
return applicableTypesForAttribute[attribute].has(this.type);
// https://html.spec.whatwg.org/multipage/input.html#common-input-element-attributes
// When an attribute doesn't apply to an input element, user agents must ignore the attribute.
_contentAttributeApplies(attribute) {
return applicableTypesForContentAttribute[attribute].has(this.type);
}

_hasAttributeAndApplies(attribute) {
return this._contentAttributeApplies(attribute) && this.hasAttributeNS(null, attribute);
}

_getAttributeIfApplies(attribute) {
if (this._contentAttributeApplies(attribute)) {
return this.getAttributeNS(null, attribute);
}
return null;
}

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

_barredFromConstraintValidationSpecialization() {
Expand All @@ -865,15 +887,15 @@ class HTMLInputElementImpl extends HTMLElementImpl {
// https://html.spec.whatwg.org/multipage/input.html#button-state-(type=button)
const willNotValidateTypes = new Set(["hidden", "reset", "button"]);
// https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly
const readOnly = this.hasAttributeNS(null, "readonly") && this._attributeApplies("readonly");
const readOnly = this._hasAttributeAndApplies("readonly");

// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled
return willNotValidateTypes.has(this.type) || readOnly;
}

// https://html.spec.whatwg.org/multipage/input.html#concept-input-required
get _required() {
return this.hasAttributeNS(null, "required");
return this._hasAttributeAndApplies("required");
}

get validity() {
Expand Down Expand Up @@ -936,17 +958,16 @@ class HTMLInputElementImpl extends HTMLElementImpl {

// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-an-overflow
// https://html.spec.whatwg.org/multipage/input.html#attr-input-max
rangeOverflow: () => this._attributeApplies("max") && this._maximum !== null &&
this._parsedValue > this._maximum,
rangeOverflow: () => this._maximum !== null && this._parsedValue > this._maximum,

// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-an-underflow
// https://html.spec.whatwg.org/multipage/input.html#attr-input-min
rangeUnderflow: () => this._attributeApplies("min") && this._minimum !== null &&
this._parsedValue < this._minimum,
rangeUnderflow: () => this._minimum !== null && this._parsedValue < this._minimum,

// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-a-pattern-mismatch
patternMismatch: () => {
if (!this.hasAttributeNS(null, "pattern") || !this._attributeApplies("pattern") || this.value === "") {
// https://html.spec.whatwg.org/multipage/input.html#the-pattern-attribute
if (this._value === "" || !this._hasAttributeAndApplies("pattern")) {
return false;
}
let regExp;
Expand All @@ -960,7 +981,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
} catch (e) {
return false;
}
if (this.type === "email" && this.hasAttributeNS(null, "multiple")) {
if (this._hasAttributeAndApplies("multiple")) {
return !splitOnCommas(this.value).every(value => regExp.test(value));
}
return !regExp.test(this.value);
Expand Down

0 comments on commit 11664ca

Please sign in to comment.