Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Use mutability as a measure for valueMissing constraint
Also make sure to check if readonly is applicable before using its
value.
  • Loading branch information
TimothyGu authored and domenic committed Jan 9, 2020
1 parent 5f8e2ab commit 678141f
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 16 deletions.
1 change: 1 addition & 0 deletions lib/jsdom/living/helpers/form-controls.js
Expand Up @@ -24,6 +24,7 @@ const { closest, firstChildWithLocalName } = require("./traversal");
const NODE_TYPE = require("../node-type");
const { HTML_NS } = require("./namespaces");

// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled
exports.isDisabled = formControl => {
if (formControl.localName === "button" || formControl.localName === "input" || formControl.localName === "select" ||
formControl.localName === "textarea") {
Expand Down
58 changes: 48 additions & 10 deletions lib/jsdom/living/nodes/HTMLInputElement-impl.js
Expand Up @@ -57,7 +57,11 @@ const applicableTypesForAttribute = {
max: maxMinStepTypes,
min: maxMinStepTypes,
step: maxMinStepTypes,
pattern: new Set(["text", "search", "tel", "url", "email", "password"])
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) {
Expand Down Expand Up @@ -182,7 +186,7 @@ class HTMLInputElementImpl extends HTMLElementImpl {
}

_activationBehavior() {
if (isDisabled(this)) {
if (!this._mutable) {
return;
}

Expand Down Expand Up @@ -294,6 +298,10 @@ class HTMLInputElementImpl extends HTMLElementImpl {
return this._otherRadioGroupElements.some(radioGroupElement => radioGroupElement.checked);
}

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

get labels() {
return getLabelsForLabelable(this);
}
Expand Down Expand Up @@ -857,33 +865,63 @@ 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");
const readOnly = this.hasAttributeNS(null, "readonly") && this._attributeApplies("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");
}

get validity() {
if (!this._validity) {
const state = {
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-missing
valueMissing: () => {
if (!this.hasAttributeNS(null, "required")) {
return false;
// https://html.spec.whatwg.org/multipage/input.html#the-required-attribute
// Constraint validation: If the element is required, and its value IDL attribute applies
// and is in the mode value, and the element is mutable, and the element's value is the
// empty string, then the element is suffering from being missing.
//
// Note: As of today, the value IDL attribute always applies.
if (this._required && valueAttributeMode(this.type) === "value" && this._mutable && this._value === "") {
return true;
}
if (this.type === "checkbox") {

switch (this.type) {
// https://html.spec.whatwg.org/multipage/input.html#checkbox-state-(type=checkbox)
// Constraint validation: If the element is required and its checkedness is
// false, then the element is suffering from being missing.
return !this.checked;
} else if (this.type === "radio") {
case "checkbox":
if (this._required && !this._checkedness) {
return true;
}
break;

// https://html.spec.whatwg.org/multipage/input.html#radio-button-state-(type=radio)
// Constraint validation: If an element in the radio button group is required,
// and all of the input elements in the radio button group have a checkedness
// that is false, then the element is suffering from being missing.
return !this._isRadioGroupChecked();
case "radio":
if (this._required && !this._isRadioGroupChecked()) {
return true;
}
break;

// https://html.spec.whatwg.org/multipage/input.html#file-upload-state-(type=file)
// Constraint validation: If the element is required and the list of selected files is
// empty, then the element is suffering from being missing.
case "file":
if (this._required && this.files.length === 0) {
return true;
}
break;
}
return this.value === "";

return false;
},

// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-too-long
Expand Down
6 changes: 5 additions & 1 deletion lib/jsdom/living/nodes/HTMLSelectElement-impl.js
Expand Up @@ -12,7 +12,7 @@ const NODE_TYPE = require("../node-type");
const HTMLCollection = require("../generated/HTMLCollection");
const HTMLOptionsCollection = require("../generated/HTMLOptionsCollection");
const { domSymbolTree } = require("../helpers/internal-constants");
const { getLabelsForLabelable, formOwner } = require("../helpers/form-controls");
const { getLabelsForLabelable, formOwner, isDisabled } = require("../helpers/form-controls");

class HTMLSelectElementImpl extends HTMLElementImpl {
constructor(globalObject, args, privateData) {
Expand Down Expand Up @@ -120,6 +120,10 @@ class HTMLSelectElementImpl extends HTMLElementImpl {
return this.hasAttributeNS(null, "multiple") ? 4 : 1;
}

get _mutable() {
return !isDisabled(this);
}

get options() {
return this._options;
}
Expand Down
8 changes: 6 additions & 2 deletions lib/jsdom/living/nodes/HTMLTextAreaElement-impl.js
Expand Up @@ -9,7 +9,7 @@ const { mixin } = require("../../utils");

const DOMException = require("domexception/webidl2js-wrapper");
const { cloningSteps } = require("../helpers/internal-constants");
const { normalizeToCRLF, getLabelsForLabelable, formOwner } = require("../helpers/form-controls");
const { isDisabled, normalizeToCRLF, getLabelsForLabelable, formOwner } = require("../helpers/form-controls");
const { childTextContent } = require("../helpers/text");
const { fireAnEvent } = require("../helpers/events");

Expand Down Expand Up @@ -207,11 +207,15 @@ class HTMLTextAreaElementImpl extends HTMLElementImpl {
return this.hasAttributeNS(null, "readonly");
}

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

// https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-required
get validity() {
if (!this._validity) {
const state = {
valueMissing: () => this.hasAttributeNS(null, "required") && this.value === ""
valueMissing: () => this.hasAttributeNS(null, "required") && this._mutable && this.value === ""
};

this._validity = ValidityState.createImpl(this._globalObject, [], {
Expand Down
3 changes: 0 additions & 3 deletions test/web-platform-tests/to-run.yaml
Expand Up @@ -557,9 +557,6 @@ formaction.html: [fail, Unknown]

DIR: html/semantics/forms/constraints

form-validation-validity-valid.html: [fail, To be fixed]
form-validation-validity-valueMissing.html: [fail, To be fixed]
form-validation-willValidate.html: [fail, To be fixed]
infinite_backtracking.html: [timeout, We cannot restrict duration of regex matching from JavaScript]

---
Expand Down

0 comments on commit 678141f

Please sign in to comment.