/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * A factory to generate FormLike objects that represent a set of related fields * which aren't necessarily marked up with a
element. FormLike's emulate * the properties of an HTMLFormElement which are relevant to form tasks. */ export let FormLikeFactory = { _propsFromForm: ["action", "autocomplete", "ownerDocument"], /** * Create a FormLike object from a . * * @param {HTMLFormElement} aForm * @return {FormLike} * @throws Error if aForm isn't an HTMLFormElement */ createFromForm(aForm) { if (!HTMLFormElement.isInstance(aForm)) { throw new Error("createFromForm: aForm must be a HTMLFormElement"); } let formLike = { elements: this.gatherFormElements(aForm), rootElement: aForm, }; for (let prop of this._propsFromForm) { formLike[prop] = aForm[prop]; } this._addToJSONProperty(formLike); return formLike; }, gatherFormElements(aForm) { // If there is no nested element, just return the form's elements. if (!aForm.querySelector("form")) { return [...aForm.elements]; } // Get all of the child input and select elements from the form. Nested // forms are normally removed during markup parsing, but could be created // if the document is modified later. Consider all of the elements that // are descendants of the form or within the form's .elements list to be // part of the form, except those with a form attribute set to a different // form. These are all added in document order. let childElements = [...aForm.querySelectorAll("input, select")]; childElements = childElements.filter( e => !e.getAttribute("form") || e.form == aForm ); // Add the element into the child elements list in document order. let index = 0; for (const formElement of aForm.elements) { if (!childElements.includes(formElement)) { // Insert elements that appear before the at the beginning and // other elements at the end. let position = aForm.compareDocumentPosition(formElement); if (position & Node.DOCUMENT_POSITION_PRECEDING) { childElements.splice(index++, 0, formElement); continue; } else { childElements.push(formElement); } } } return childElements; }, /** * Create a FormLike object from an element that is the root of the document * * Currently all not in a are one LoginForm but this * shouldn't be relied upon as the heuristics may change to detect multiple * "forms" (e.g. registration and login) on one page with a . * * @param {HTMLElement} aDocumentRoot * @param {Object} aOptions * @param {boolean} [aOptions.ignoreForm = false] * True to always use owner document as the `form` * @return {formLike} * @throws Error if aDocumentRoot is null */ createFromDocumentRoot(aDocumentRoot, aOptions = {}) { if (!aDocumentRoot) { throw new Error("createFromDocumentRoot: aDocumentRoot is null"); } let formLike = { action: aDocumentRoot.baseURI, autocomplete: "on", ownerDocument: aDocumentRoot.ownerDocument, rootElement: aDocumentRoot, }; // FormLikes can be created when fields are inserted into the DOM. When // many, many fields are inserted one after the other, we create many // FormLikes, and computing the elements list becomes more and more // expensive. Making the elements list lazy means that it'll only // be computed when it's eventually needed (if ever). ChromeUtils.defineLazyGetter(formLike, "elements", function () { let elements = []; for (let el of aDocumentRoot.querySelectorAll( "input, select, textarea" )) { // Exclude elements inside the rootElement that are already in a as // they will be handled by their own FormLike. if (!el.form || aOptions.ignoreForm) { elements.push(el); } } return elements; }); this._addToJSONProperty(formLike); return formLike; }, /** * Create a FormLike object from an //,