summaryrefslogtreecommitdiff
path: root/alarm/node_modules/jsdom/lib/jsdom/living/nodes/HTMLInputElement-impl.js
diff options
context:
space:
mode:
Diffstat (limited to 'alarm/node_modules/jsdom/lib/jsdom/living/nodes/HTMLInputElement-impl.js')
-rw-r--r--alarm/node_modules/jsdom/lib/jsdom/living/nodes/HTMLInputElement-impl.js1128
1 files changed, 1128 insertions, 0 deletions
diff --git a/alarm/node_modules/jsdom/lib/jsdom/living/nodes/HTMLInputElement-impl.js b/alarm/node_modules/jsdom/lib/jsdom/living/nodes/HTMLInputElement-impl.js
new file mode 100644
index 0000000..edd299d
--- /dev/null
+++ b/alarm/node_modules/jsdom/lib/jsdom/living/nodes/HTMLInputElement-impl.js
@@ -0,0 +1,1128 @@
+"use strict";
+const DOMException = require("domexception/webidl2js-wrapper");
+const FileList = require("../generated/FileList");
+const Decimal = require("decimal.js");
+const HTMLElementImpl = require("./HTMLElement-impl").implementation;
+const idlUtils = require("../generated/utils");
+const DefaultConstraintValidationImpl =
+ require("../constraint-validation/DefaultConstraintValidation-impl").implementation;
+const ValidityState = require("../generated/ValidityState");
+const { mixin } = require("../../utils");
+const { domSymbolTree, cloningSteps } = require("../helpers/internal-constants");
+const { getLabelsForLabelable, formOwner } = require("../helpers/form-controls");
+const { fireAnEvent } = require("../helpers/events");
+const {
+ isDisabled,
+ isValidEmailAddress,
+ isValidAbsoluteURL,
+ sanitizeValueByType
+} = require("../helpers/form-controls");
+const {
+ asciiCaseInsensitiveMatch,
+ asciiLowercase,
+ parseFloatingPointNumber,
+ splitOnCommas
+} = require("../helpers/strings");
+const { isDate } = require("../helpers/dates-and-times");
+const {
+ convertStringToNumberByType,
+ convertStringToDateByType,
+ serializeDateByType,
+ convertNumberToStringByType
+} = require("../helpers/number-and-date-inputs");
+
+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", "date",
+ "month", "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio",
+ "file", "submit", "image", "reset", "button"
+]);
+
+// https://html.spec.whatwg.org/multipage/input.html#concept-input-apply
+
+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
+};
+
+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"]);
+
+function valueAttributeMode(type) {
+ if (valueAttributeDefaultMode.has(type)) {
+ return "default";
+ }
+ if (valueAttributeDefaultOnMode.has(type)) {
+ return "default/on";
+ }
+ if (type === "file") {
+ return "filename";
+ }
+ 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);
+
+ this._selectionStart = this._selectionEnd = 0;
+ this._selectionDirection = "none";
+ this._value = "";
+ this._dirtyValue = false;
+ this._checkedness = false;
+ this._dirtyCheckedness = false;
+
+ this._preCheckedRadioState = null;
+ this._legacyActivationBehaviorPreviousIndeterminateState = false;
+
+ this.indeterminate = false;
+
+ this._customValidityErrorMessage = "";
+
+ this._labels = null;
+
+ this._hasActivationBehavior = true;
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number
+ get _convertStringToNumber() {
+ return convertStringToNumberByType[this.type];
+ }
+
+ get _convertNumberToString() {
+ return convertNumberToStringByType[this.type];
+ }
+
+ get _convertDateToString() {
+ return serializeDateByType[this.type];
+ }
+
+ get _convertStringToDate() {
+ return convertStringToDateByType[this.type];
+ }
+
+ _isStepAligned(v) {
+ return new Decimal(v).minus(this._stepBase)
+ .modulo(this._allowedValueStep)
+ .isZero();
+ }
+
+ // Returns a Decimal.
+ _stepAlign(v, roundUp) {
+ const allowedValueStep = this._allowedValueStep;
+ const stepBase = this._stepBase;
+
+ return new Decimal(v).minus(stepBase)
+ .toNearest(allowedValueStep, roundUp ? Decimal.ROUND_UP : Decimal.ROUND_DOWN)
+ .add(stepBase);
+ }
+
+ // For <input>, https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-value
+ // is a simple value that is gotten and set, not computed.
+ _getValue() {
+ return this._value;
+ }
+
+ _legacyPreActivationBehavior() {
+ if (this.type === "checkbox") {
+ this.checked = !this.checked;
+ this._legacyActivationBehaviorPreviousIndeterminateState = this.indeterminate;
+ this.indeterminate = false;
+ } else if (this.type === "radio") {
+ this._preCheckedRadioState = this.checked;
+ this.checked = true;
+ }
+ }
+
+ _legacyCanceledActivationBehavior() {
+ if (this.type === "checkbox") {
+ this.checked = !this.checked;
+ this.indeterminate = this._legacyActivationBehaviorPreviousIndeterminateState;
+ } else if (this.type === "radio") {
+ if (this._preCheckedRadioState !== null) {
+ this.checked = this._preCheckedRadioState;
+ this._preCheckedRadioState = null;
+ }
+ }
+ }
+
+ _activationBehavior() {
+ if (!this._mutable && this.type !== "checkbox" && this.type !== "radio") {
+ return;
+ }
+
+ const { form } = this;
+
+ if (this.type === "checkbox" || (this.type === "radio" && !this._preCheckedRadioState)) {
+ if (this.isConnected) {
+ fireAnEvent("input", this, undefined, { bubbles: true });
+ fireAnEvent("change", this, undefined, { bubbles: true });
+ }
+ } else if (form && this.type === "submit") {
+ form._doSubmit();
+ } else if (form && this.type === "reset") {
+ form._doReset();
+ }
+ }
+
+ _attrModified(name, value, oldVal) {
+ const wrapper = idlUtils.wrapperForImpl(this);
+ if (!this._dirtyValue && name === "value") {
+ this._value = sanitizeValueByType(this, wrapper.defaultValue);
+ }
+ if (!this._dirtyCheckedness && name === "checked") {
+ this._checkedness = wrapper.defaultChecked;
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ }
+
+ if (name === "name" || name === "type") {
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ }
+
+ 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(name, value, oldVal);
+ }
+
+ // 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);
+ this._dirtyValue = false;
+ this._checkedness = wrapper.defaultChecked;
+ this._dirtyCheckedness = false;
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ }
+
+ _changedFormOwner() {
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ }
+
+ get _otherRadioGroupElements() {
+ const wrapper = idlUtils.wrapperForImpl(this);
+ const root = this._radioButtonGroupRoot;
+ if (!root) {
+ return [];
+ }
+
+ const result = [];
+
+ const descendants = domSymbolTree.treeIterator(root);
+ for (const candidate of descendants) {
+ if (candidate._radioButtonGroupRoot !== root) {
+ continue;
+ }
+
+ const candidateWrapper = idlUtils.wrapperForImpl(candidate);
+ if (!candidateWrapper.name || candidateWrapper.name !== wrapper.name) {
+ continue;
+ }
+
+ if (candidate !== this) {
+ result.push(candidate);
+ }
+ }
+ return result;
+ }
+
+ _removeOtherRadioCheckedness() {
+ for (const radioGroupElement of this._otherRadioGroupElements) {
+ radioGroupElement._checkedness = false;
+ }
+ }
+
+ get _radioButtonGroupRoot() {
+ const wrapper = idlUtils.wrapperForImpl(this);
+ if (this.type !== "radio" || !wrapper.name) {
+ return null;
+ }
+
+ let e = domSymbolTree.parent(this);
+ while (e) {
+ // root node of this home sub tree
+ // or the form element we belong to
+ if (!domSymbolTree.parent(e) || e.nodeName.toUpperCase() === "FORM") {
+ return e;
+ }
+ e = domSymbolTree.parent(e);
+ }
+ return null;
+ }
+
+ _someInRadioGroup(name) {
+ if (this[name]) {
+ return true;
+ }
+ return this._otherRadioGroupElements.some(radioGroupElement => radioGroupElement[name]);
+ }
+
+ get _mutable() {
+ return !isDisabled(this) && !this._hasAttributeAndApplies("readonly");
+ }
+
+ get labels() {
+ return getLabelsForLabelable(this);
+ }
+
+ get form() {
+ return formOwner(this);
+ }
+
+ get checked() {
+ return this._checkedness;
+ }
+
+ set checked(checked) {
+ this._checkedness = Boolean(checked);
+ this._dirtyCheckedness = true;
+ if (this._checkedness) {
+ this._removeOtherRadioCheckedness();
+ }
+ }
+
+ get value() {
+ switch (valueAttributeMode(this.type)) {
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
+ case "value":
+ return this._getValue();
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
+ case "default": {
+ const attr = this.getAttributeNS(null, "value");
+ return attr !== null ? attr : "";
+ }
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
+ case "default/on": {
+ const attr = this.getAttributeNS(null, "value");
+ return attr !== null ? attr : "on";
+ }
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
+ case "filename":
+ return this.files.length ? "C:\\fakepath\\" + this.files[0].name : "";
+ default:
+ throw new Error("jsdom internal error: unknown value attribute mode");
+ }
+ }
+
+ set value(val) {
+ switch (valueAttributeMode(this.type)) {
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
+ case "value": {
+ const oldValue = this._value;
+ this._value = sanitizeValueByType(this, val);
+ this._dirtyValue = true;
+
+ if (oldValue !== this._value) {
+ this._selectionStart = this._selectionEnd = this._getValueLength();
+ this._selectionDirection = "none";
+ }
+ break;
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
+ case "default":
+ case "default/on":
+ this.setAttributeNS(null, "value", val);
+ break;
+
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
+ case "filename":
+ if (val === "") {
+ this.files.length = 0;
+ } else {
+ throw DOMException.create(this._globalObject, [
+ "This input element accepts a filename, which may only be programmatically set to the empty string.",
+ "InvalidStateError"
+ ]);
+ }
+ break;
+
+ default:
+ throw new Error("jsdom internal error: unknown value attribute mode");
+ }
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasdate
+ get valueAsDate() {
+ if (!this._idlMemberApplies("valueAsDate")) {
+ return null;
+ }
+
+ const window = this._ownerDocument._defaultView;
+ const convertedValue = this._convertStringToDate(this._value);
+
+ if (convertedValue instanceof Date) {
+ return new window.Date(convertedValue.getTime());
+ }
+
+ return null;
+ }
+
+ set valueAsDate(v) {
+ 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.",
+ "InvalidStateError"
+ ]);
+ }
+
+ if (v !== null && !isDate(v)) {
+ throw new TypeError("Failed to set the 'valueAsDate' property on 'HTMLInputElement': The provided value is " +
+ "not a Date.");
+ }
+
+ if (v === null || isNaN(v)) {
+ this._value = "";
+ }
+
+ this._value = this._convertDateToString(v);
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasnumber
+ get valueAsNumber() {
+ if (!this._idlMemberApplies("valueAsNumber")) {
+ return NaN;
+ }
+
+ const parsedValue = this._convertStringToNumber(this._value);
+ return parsedValue !== null ? parsedValue : NaN;
+ }
+
+ set valueAsNumber(v) {
+ if (!isFinite(v)) {
+ throw new TypeError("Failed to set infinite value as Number");
+ }
+
+ 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.",
+ "InvalidStateError"
+ ]);
+ }
+
+ this._value = this._convertNumberToString(v);
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#dom-input-stepup
+ _stepUpdate(n, isUp) {
+ const methodName = isUp ? "stepUp" : "stepDown";
+ if (!this._idlMemberApplies(methodName)) {
+ throw DOMException.create(this._globalObject, [
+ `Failed to invoke '${methodName}' method on 'HTMLInputElement': ` +
+ "This input element does not support Number values.",
+ "InvalidStateError"
+ ]);
+ }
+
+ const allowedValueStep = this._allowedValueStep;
+ if (allowedValueStep === null) {
+ throw DOMException.create(this._globalObject, [
+ `Failed to invoke '${methodName}' method on 'HTMLInputElement': ` +
+ "This input element does not support value step.",
+ "InvalidStateError"
+ ]);
+ }
+
+ const min = this._minimum;
+ const max = this._maximum;
+
+ if (min !== null && max !== null) {
+ if (min > max) {
+ return;
+ }
+
+ const candidateStepValue = this._stepAlign(Decimal.add(min, allowedValueStep), /* roundUp = */ false);
+ if (candidateStepValue.lt(min) || candidateStepValue.gt(max)) {
+ return;
+ }
+ }
+
+ let value = 0;
+ try {
+ value = this.valueAsNumber;
+ if (isNaN(value)) { // Empty value is parsed as NaN.
+ value = 0;
+ }
+ } catch (error) {
+ // Step 5. Default value is 0.
+ }
+ value = new Decimal(value);
+
+ const valueBeforeStepping = value;
+
+ if (!this._isStepAligned(value)) {
+ value = this._stepAlign(value, /* roundUp = */ isUp);
+ } else {
+ let delta = Decimal.mul(n, allowedValueStep);
+ if (!isUp) {
+ delta = delta.neg();
+ }
+ value = value.add(delta);
+ }
+
+ if (min !== null && value.lt(min)) {
+ value = this._stepAlign(min, /* roundUp = */ true);
+ }
+
+ if (max !== null && value.gt(max)) {
+ value = this._stepAlign(max, /* roundUp = */ false);
+ }
+
+ if (isUp ? value.lt(valueBeforeStepping) : value.gt(valueBeforeStepping)) {
+ return;
+ }
+
+ this._value = this._convertNumberToString(value.toNumber());
+ }
+
+ stepDown(n = 1) {
+ return this._stepUpdate(n, false);
+ }
+
+ stepUp(n = 1) {
+ return this._stepUpdate(n, true);
+ }
+
+ get files() {
+ if (this.type === "file") {
+ this[filesSymbol] = this[filesSymbol] || FileList.createImpl(this._globalObject);
+ } else {
+ this[filesSymbol] = null;
+ }
+ return this[filesSymbol];
+ }
+
+ set files(value) {
+ if (this.type === "file" && value !== null) {
+ this[filesSymbol] = value;
+ }
+ }
+
+ get type() {
+ const typeAttribute = this.getAttributeNS(null, "type");
+ return getTypeFromAttribute(typeAttribute);
+ }
+
+ set type(type) {
+ this.setAttributeNS(null, "type", type);
+ }
+
+ _dispatchSelectEvent() {
+ fireAnEvent("select", this, undefined, { bubbles: true, cancelable: true });
+ }
+
+ _getValueLength() {
+ return typeof this.value === "string" ? this.value.length : 0;
+ }
+
+ select() {
+ if (!this._idlMemberApplies("select")) {
+ return;
+ }
+
+ this._selectionStart = 0;
+ this._selectionEnd = this._getValueLength();
+ this._selectionDirection = "none";
+ this._dispatchSelectEvent();
+ }
+
+ get selectionStart() {
+ if (!this._idlMemberApplies("selectionStart")) {
+ return null;
+ }
+
+ return this._selectionStart;
+ }
+
+ set selectionStart(start) {
+ 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 (!this._idlMemberApplies("selectionEnd")) {
+ return null;
+ }
+
+ return this._selectionEnd;
+ }
+
+ set selectionEnd(end) {
+ 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 (!this._idlMemberApplies("selectionDirection")) {
+ return null;
+ }
+
+ return this._selectionDirection;
+ }
+
+ set selectionDirection(dir) {
+ 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 (!this._idlMemberApplies("setSelectionRange")) {
+ throw DOMException.create(this._globalObject, ["The object is in an invalid state.", "InvalidStateError"]);
+ }
+
+ this._selectionEnd = Math.min(end, this._getValueLength());
+ this._selectionStart = Math.min(start, this._selectionEnd);
+ this._selectionDirection = dir === "forward" || dir === "backward" ? dir : "none";
+ this._dispatchSelectEvent();
+ }
+
+ setRangeText(repl, start, end, selectionMode = "preserve") {
+ if (!this._idlMemberApplies("setRangeText")) {
+ throw DOMException.create(this._globalObject, ["The object is in an invalid state.", "InvalidStateError"]);
+ }
+
+ if (arguments.length < 2) {
+ start = this._selectionStart;
+ end = this._selectionEnd;
+ } else if (start > end) {
+ throw DOMException.create(this._globalObject, ["The index is not in the allowed range.", "IndexSizeError"]);
+ }
+
+ start = Math.min(start, this._getValueLength());
+ end = Math.min(end, this._getValueLength());
+
+ const val = this.value;
+ let selStart = this._selectionStart;
+ let selEnd = this._selectionEnd;
+
+ this.value = val.slice(0, start) + repl + val.slice(end);
+
+ const newEnd = start + this.value.length;
+
+ if (selectionMode === "select") {
+ this.setSelectionRange(start, newEnd);
+ } else if (selectionMode === "start") {
+ this.setSelectionRange(start, start);
+ } else if (selectionMode === "end") {
+ this.setSelectionRange(newEnd, newEnd);
+ } else { // preserve
+ const delta = repl.length - (end - start);
+
+ if (selStart > end) {
+ selStart += delta;
+ } else if (selStart > start) {
+ selStart = start;
+ }
+
+ if (selEnd > end) {
+ selEnd += delta;
+ } else if (selEnd > start) {
+ selEnd = newEnd;
+ }
+
+ this.setSelectionRange(selStart, selEnd);
+ }
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#the-list-attribute
+ get list() {
+ const id = this._getAttributeIfApplies("list");
+ if (!id) {
+ return null;
+ }
+
+ const el = this.getRootNode({}).getElementById(id);
+
+ if (el && el.localName === "datalist") {
+ return el;
+ }
+
+ return null;
+ }
+
+ // 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
+ }
+ return parseInt(this.getAttributeNS(null, "maxlength"));
+ }
+
+ set maxLength(value) {
+ if (value < 0) {
+ throw DOMException.create(this._globalObject, ["The index is not in the allowed range.", "IndexSizeError"]);
+ }
+ this.setAttributeNS(null, "maxlength", String(value));
+ }
+
+ get minLength() {
+ if (!this.hasAttributeNS(null, "minlength")) {
+ return 0;
+ }
+ return parseInt(this.getAttributeNS(null, "minlength"));
+ }
+
+ set minLength(value) {
+ if (value < 0) {
+ throw DOMException.create(this._globalObject, ["The index is not in the allowed range.", "IndexSizeError"]);
+ }
+ this.setAttributeNS(null, "minlength", String(value));
+ }
+
+ get size() {
+ if (!this.hasAttributeNS(null, "size")) {
+ return 20;
+ }
+ return parseInt(this.getAttributeNS(null, "size"));
+ }
+
+ set size(value) {
+ if (value <= 0) {
+ throw DOMException.create(this._globalObject, ["The index is not in the allowed range.", "IndexSizeError"]);
+ }
+ this.setAttributeNS(null, "size", String(value));
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes
+ get _minimum() {
+ let min = this._defaultMinimum;
+ const attr = this._getAttributeIfApplies("min");
+ if (attr !== null && this._convertStringToNumber !== undefined) {
+ const parsed = this._convertStringToNumber(attr);
+ if (parsed !== null) {
+ min = parsed;
+ }
+ }
+ return min;
+ }
+
+ get _maximum() {
+ let max = this._defaultMaximum;
+ const attr = this._getAttributeIfApplies("max");
+ if (attr !== null && this._convertStringToNumber !== undefined) {
+ const parsed = this._convertStringToNumber(attr);
+ if (parsed !== null) {
+ max = parsed;
+ }
+ }
+ return max;
+ }
+
+ get _defaultMinimum() {
+ if (this.type === "range") {
+ return 0;
+ }
+ return null;
+ }
+
+ get _defaultMaximum() {
+ if (this.type === "range") {
+ return 100;
+ }
+ return null;
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#concept-input-step
+ get _allowedValueStep() {
+ if (!this._contentAttributeApplies("step")) {
+ return null;
+ }
+ const attr = this.getAttributeNS(null, "step");
+ if (attr === null) {
+ return this._defaultStep * this._stepScaleFactor;
+ }
+ if (asciiCaseInsensitiveMatch(attr, "any")) {
+ return null;
+ }
+ const parsedStep = parseFloatingPointNumber(attr);
+ if (parsedStep === null || parsedStep <= 0) {
+ return this._defaultStep * this._stepScaleFactor;
+ }
+ return parsedStep * this._stepScaleFactor;
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#concept-input-step-scale
+ get _stepScaleFactor() {
+ const dayInMilliseconds = 24 * 60 * 60 * 1000;
+ switch (this.type) {
+ case "week":
+ return 7 * dayInMilliseconds;
+ case "date":
+ return dayInMilliseconds;
+ case "datetime-local":
+ case "datetime":
+ case "time":
+ return 1000;
+ }
+ return 1;
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#concept-input-step-default
+ get _defaultStep() {
+ if (this.type === "datetime-local" || this.type === "datetime" || this.type === "time") {
+ return 60;
+ }
+ return 1;
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#concept-input-min-zero
+ get _stepBase() {
+ if (this._hasAttributeAndApplies("min")) {
+ const min = this._convertStringToNumber(this.getAttributeNS(null, "min"));
+ if (min !== null) {
+ return min;
+ }
+ }
+ if (this.hasAttributeNS(null, "value")) {
+ const value = this._convertStringToNumber(this.getAttributeNS(null, "value"));
+ if (value !== null) {
+ return value;
+ }
+ }
+ if (this._defaultStepBase !== null) {
+ return this._defaultStepBase;
+ }
+ return 0;
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#concept-input-step-default-base
+ get _defaultStepBase() {
+ if (this.type === "week") {
+ // The start of week 1970-W01
+ return -259200000;
+ }
+ return null;
+ }
+
+ // 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, type = this.type) {
+ return applicableTypesForIDLMember[member].has(type);
+ }
+
+ _barredFromConstraintValidationSpecialization() {
+ // https://html.spec.whatwg.org/multipage/input.html#hidden-state-(type=hidden)
+ // https://html.spec.whatwg.org/multipage/input.html#reset-button-state-(type=reset)
+ // 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._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._hasAttributeAndApplies("required");
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#has-a-periodic-domain
+ get _hasAPeriodicDomain() {
+ return this.type === "time";
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#has-a-reversed-range
+ get _hasAReversedRange() {
+ return this._hasAPeriodicDomain && this._maximum < this._minimum;
+ }
+
+ get validity() {
+ if (!this._validity) {
+ // Constraint validation: When an element has a reversed range, and the result of applying
+ // the algorithm to convert a string to a number to the string given by the element's value
+ // is a number, and the number obtained from that algorithm is more than the maximum and less
+ // than the minimum, the element is simultaneously suffering from an underflow and suffering
+ // from an overflow.
+ const reversedRangeSufferingOverUnderflow = () => {
+ const parsedValue = this._convertStringToNumber(this._value);
+ return parsedValue !== null && parsedValue > this._maximum && parsedValue < this._minimum;
+ };
+
+ const state = {
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-missing
+ valueMissing: () => {
+ // 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;
+ }
+
+ 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.
+ 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.
+ case "radio":
+ if (this._someInRadioGroup("_required") && !this._someInRadioGroup("checked")) {
+ 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 false;
+ },
+
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-too-long
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-maxlength
+ // jsdom has no way at the moment to emulate a user interaction, so tooLong/tooShort have
+ // to be set to false.
+ tooLong: () => false,
+
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-too-short
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-minlength
+ tooShort: () => false,
+
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-an-overflow
+ rangeOverflow: () => {
+ // https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes
+ if (this._hasAReversedRange) {
+ return reversedRangeSufferingOverUnderflow();
+ }
+ // Constraint validation: When the element has a maximum and does not have a reversed
+ // range, and the result of applying the algorithm to convert a string to a number to the
+ // string given by the element's value is a number, and the number obtained from that
+ // algorithm is more than the maximum, the element is suffering from an overflow.
+ if (this._maximum !== null) {
+ const parsedValue = this._convertStringToNumber(this._value);
+ if (parsedValue !== null && parsedValue > this._maximum) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-an-underflow
+ rangeUnderflow: () => {
+ // https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes
+ if (this._hasAReversedRange) {
+ return reversedRangeSufferingOverUnderflow();
+ }
+ // Constraint validation: When the element has a minimum and does not have a reversed
+ // range, and the result of applying the algorithm to convert a string to a number to the
+ // string given by the element's value is a number, and the number obtained from that
+ // algorithm is less than the minimum, the element is suffering from an underflow.
+ if (this._minimum !== null) {
+ const parsedValue = this._convertStringToNumber(this._value);
+ if (parsedValue !== null && parsedValue < this._minimum) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-a-pattern-mismatch
+ patternMismatch: () => {
+ // https://html.spec.whatwg.org/multipage/input.html#the-pattern-attribute
+ if (this._value === "" || !this._hasAttributeAndApplies("pattern")) {
+ return false;
+ }
+ let regExp;
+ try {
+ const pattern = this.getAttributeNS(null, "pattern");
+ // The pattern attribute should be matched against the entire value, not just any
+ // subset, so add ^ and $ anchors. But also check the validity of the regex itself
+ // first.
+ new RegExp(pattern, "u"); // eslint-disable-line no-new
+ regExp = new RegExp("^(?:" + pattern + ")$", "u");
+ } catch (e) {
+ return false;
+ }
+ if (this._hasAttributeAndApplies("multiple")) {
+ return !splitOnCommas(this._value).every(value => regExp.test(value));
+ }
+ return !regExp.test(this._value);
+ },
+
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-a-step-mismatch
+ // https://html.spec.whatwg.org/multipage/input.html#attr-input-step
+ stepMismatch: () => {
+ const allowedValueStep = this._allowedValueStep;
+ if (allowedValueStep === null) {
+ return false;
+ }
+ const number = this._convertStringToNumber(this._value);
+ return number !== null && !this._isStepAligned(number);
+ },
+
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-a-type-mismatch
+ typeMismatch: () => {
+ switch (this.type) {
+ // https://html.spec.whatwg.org/multipage/input.html#url-state-(type=url)
+ // Constraint validation: While the value of the element is neither the empty string
+ // nor a valid absolute URL, the element is suffering from a type mismatch.
+ case "url":
+ if (this._value !== "" && !isValidAbsoluteURL(this._value)) {
+ return true;
+ }
+ break;
+
+ // https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type=email)
+ // Constraint validation [multiple=false]: While the value of the element is neither the empty
+ // string nor a single valid e - mail address, the element is suffering from a type mismatch.
+ // Constraint validation [multiple=true]: While the value of the element is not a valid e-mail address list,
+ // the element is suffering from a type mismatch.
+ case "email":
+ if (this._value !== "" && !isValidEmailAddress(this._getValue(), this.hasAttributeNS(null, "multiple"))) {
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+ };
+
+ this._validity = ValidityState.createImpl(this._globalObject, [], {
+ element: this,
+ state
+ });
+ }
+ return this._validity;
+ }
+
+ [cloningSteps](copy, node) {
+ copy._value = node._value;
+ copy._checkedness = node._checkedness;
+ copy._dirtyValue = node._dirtyValue;
+ copy._dirtyCheckedness = node._dirtyCheckedness;
+ }
+}
+
+mixin(HTMLInputElementImpl.prototype, DefaultConstraintValidationImpl.prototype);
+
+module.exports = {
+ implementation: HTMLInputElementImpl
+};