/*
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 *           (C) 2001 Dirk Mueller (mueller@kde.org)
 * Copyright (C) 2004-2010, 2012-2016 Apple Inc. All rights reserved.
 *           (C) 2006 Alexey Proskuryakov (ap@nypop.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "HTMLFormElement.h"

#include "Attribute.h"
#include "DOMFormData.h"
#include "DOMTokenList.h"
#include "DOMWindow.h"
#include "Document.h"
#include "ElementTraversal.h"
#include "Event.h"
#include "EventNames.h"
#include "FileList.h"
#include "FileSystem.h"
#include "FormController.h"
#include "FormData.h"
#include "FormDataList.h"
#include "FormState.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "HTMLDialogElement.h"
#include "HTMLFormControlsCollection.h"
#include "HTMLDocument.h"
#include "HTMLImageElement.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "JSHTMLFormElement.h"
#include "JSRadioNodeList.h"
#include "MIMETypeRegistry.h"
#include "MixedContentChecker.h"
#include "NodeRareData.h"
#include "Page.h"
#include "PageConsoleClient.h"
#include "RadioNodeList.h"
#include "RenderTextControl.h"
#include "RuntimeEnabledFeatures.h"
#include "ScriptDisallowedScope.h"
#include "Settings.h"
#include "ValidityState.h"
#include <limits>
#include <wtf/SetForScope.h>

#if PLATFORM(WX)
#include <wx/defs.h>
#include <wx/filename.h>
#endif

using namespace std;

namespace WebCore {

using namespace HTMLNames;

struct FormRelAttributes {
    bool noopener { false };
    bool noreferrer { false };
    bool opener { false };
};

static FormRelAttributes parseFormRelAttributes(StringView string)
{
    FormRelAttributes attributes;
    for (auto token : string.split(' ')) {
        if (equalIgnoringASCIICase(token, "noopener"))
            attributes.noopener = true;
        else if (equalIgnoringASCIICase(token, "noreferrer"))
            attributes.noreferrer = true;
        else if (equalIgnoringASCIICase(token, "opener"))
            attributes.opener = true;
    }
    return attributes;
}

inline HTMLFormElement::HTMLFormElement(const QualifiedName& tagName, Document* document)
    : HTMLElement(tagName, document)
{
    ASSERT(hasTagName(formTag));
}

PassRefPtr<HTMLFormElement> HTMLFormElement::create(Document* document)
{
    return adoptRef(new HTMLFormElement(formTag, document));
}

PassRefPtr<HTMLFormElement> HTMLFormElement::create(const QualifiedName& tagName, Document* document)
{
    return adoptRef(new HTMLFormElement(tagName, document));
}

HTMLFormElement::~HTMLFormElement()
{
    document().formController().willDeleteForm(this);
    if (!shouldAutocomplete())
        document().unregisterForPageCacheSuspensionCallbacks(*this);

    m_defaultButton = nullptr;
    for (auto& weakElement : m_associatedElements) {
    	RefPtr<HTMLElement> element = weakElement.get();
    	ASSERT(element);
        auto* associatedElement = element->asFormAssociatedElement();
        ASSERT(associatedElement);
        associatedElement->formWillBeDestroyed();
    }
    for (auto& imageElement : m_imageElements)
		imageElement->m_form = nullptr;
}

bool HTMLFormElement::formWouldHaveSecureSubmission(const String& url)
{
    return document().completeURL(url).protocolIs("https");
}

Node::InsertedIntoResult HTMLFormElement::insertedIntoAncestor(const InsertionType& insertionType, ContainerNode& parentOfInsertedTree)
{
    HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
    if (insertionType.connectedToDocument)
        document().didAssociateFormControl(*this);
    return InsertedIntoResult::Done;
}

void HTMLFormElement::removedFromAncestor(const RemovalType& removalType, ContainerNode& parentOfRemovedTree)
{
	Node& root = traverseToRootNode(); // Do not rely on rootNode() because our IsInTreeScope is outdated.
	for (auto& element : copyAssociatedElementsVector())
        element->formOwnerRemovedFromTree(root);
    HTMLElement::removedFromAncestor(removalType, parentOfRemovedTree);
}

unsigned HTMLFormElement::length() const
{
    unsigned len = 0;
    for (auto& weakElement : m_associatedElements) {
        RefPtr<HTMLElement> element = weakElement.get();
        ASSERT(element);
        auto* associatedElement = element->asFormAssociatedElement();
        ASSERT(associatedElement);
        if (associatedElement->isEnumeratable())
            ++len;
    }
    return len;
}

Node* HTMLFormElement::item(unsigned index)
{
    return elements()->item(index);
}

JSC::JSValue /*std::optional<Variant<RefPtr<RadioNodeList>, RefPtr<Element>>>*/ HTMLFormElement::namedItem(JSC::ExecState* exec, JSHTMLFormElement* thisObject, const AtomicString& name)
{
	Vector<RefPtr<Element>> namedItems = namedElements(name);

    if (namedItems.isEmpty())
        return JSC::JSValue();
    if (namedItems.size() == 1)
    	return toJS(exec, thisObject->globalObject(), namedItems[0].get());

    return toJS(exec, thisObject->globalObject(), radioNodeList(name).get());
}

Vector<AtomicString> HTMLFormElement::supportedPropertyNames() const
{
    // FIXME: Should be implemented (only needed for enumeration with includeDontEnumProperties mode
    // since this class is annotated with LegacyUnenumerableNamedProperties).
    return Vector<AtomicString>();
}

void HTMLFormElement::submitImplicitly(Event* event, bool fromImplicitSubmissionTrigger)
{
    int submissionTriggerCount = 0;
    for (unsigned i = 0; i < m_associatedElements.size(); ++i) {
    	WeakPtr<HTMLElement>& formAssociatedElement = m_associatedElements[i];
        if (!formAssociatedElement->isFormControlElement())
            continue;
        HTMLFormControlElement& formElement = toHTMLFormControlElement(*formAssociatedElement);
        if (formElement.isSuccessfulSubmitButton()) {
			formElement.dispatchSimulatedClick(event);
			return;
        } else if (formElement.canTriggerImplicitSubmission())
            ++submissionTriggerCount;
    }
    if (fromImplicitSubmissionTrigger && submissionTriggerCount == 1)
    	submitIfPossible(event);
}

static inline HTMLFormControlElement* submitElementFromEvent(const Event* event)
{
	RefPtr<Node> targetNode = event->target()->toNode();
    if (!targetNode || !targetNode->isElementNode())
        return 0;
    Element& targetElement = toElement(*targetNode);
    if (!targetElement.isFormControlElement())
        return 0;
    return static_cast<HTMLFormControlElement*>(&targetElement);
}

bool HTMLFormElement::validateInteractively()
{
	for (auto& associatedElement : m_associatedElements) {
        if (associatedElement->isFormControlElement())
            toHTMLFormControlElement(*associatedElement).hideVisibleValidationMessage();
    }

    Vector<RefPtr<HTMLFormControlElement>> unhandledInvalidControls;
    if (!checkInvalidControlsAndCollectUnhandled(unhandledInvalidControls))
        return true;
    // Because the form has invalid controls, we abort the form submission and
    // show a validation message on a focusable form control.

    // Needs to update layout now because we'd like to call isFocusable(), which
    // has !renderer()->needsLayout() assertion.
    document().updateLayoutIgnorePendingStylesheets();

    RefPtr<HTMLFormElement> protector(this);

    // Focus on the first focusable control and show a validation message.
    for (auto& control : unhandledInvalidControls) {
		if (control->inDocument() && control->isFocusable()) {
			control->focusAndShowValidationMessage();
			break;
		}
	}

    // Warn about all of unfocusable controls.
    if (document().frame()) {
        for (auto& control : unhandledInvalidControls) {
            if (control->inDocument() && control->isFocusable())
                continue;
            String message = tryMakeString("An invalid form control with name='", control->name(), "' is not focusable.");
            document().addConsoleMessage(MessageSource::Rendering, MessageType::Log, MessageLevel::Error, message);
        }
    }

    return false;
}

void HTMLFormElement::submitIfPossible(Event* event, HTMLFormControlElement* submitter, FormSubmissionTrigger trigger)
{
	RefPtr<Frame> frame = document().frame();
    if (m_isSubmittingOrPreparingForSubmission || !frame)
        return;

    m_isSubmittingOrPreparingForSubmission = true;
    m_shouldSubmit = false;

    bool shouldValidate = document().page() && document().page()->settings().interactiveFormValidationEnabled() && !noValidate();

    if (shouldValidate) {
        RefPtr<HTMLFormControlElement> submitElement = submitter ? submitter : findSubmitter(event);
        if (submitElement && submitElement->formNoValidate())
            shouldValidate = false;
    }

    // Interactive validation must be done before dispatching the submit event.
    if (shouldValidate && !validateInteractively()) {
        m_isSubmittingOrPreparingForSubmission = false;
        return;
    }

    StringPairVector controlNamesAndValues;
    getTextFieldValues(controlNamesAndValues);

    auto targetFrame = frame->loader()->findFrameForNavigation(effectiveTarget(event, submitter), &document());
    if (!targetFrame)
        targetFrame = frame.get();
    RefPtr<FormState> formState = FormState::create(*this, controlNamesAndValues, document(), NotSubmittedByJavaScript);
    targetFrame->loader()->client()->dispatchWillSendSubmitEvent(formState.release());

    RefPtr<HTMLFormElement> protectedThis(this);

    RefPtr<Event> submitEvent = Event::create(eventNames().submitEvent, true, true);
    dispatchEvent(submitEvent);

    // Event handling could have resulted in m_shouldSubmit becoming true as a side effect, too.
    if (!submitEvent->defaultPrevented())
        m_shouldSubmit = true;

    m_isSubmittingOrPreparingForSubmission = false;

    if (m_shouldSubmit)
        submit(event, true, !submitter, trigger, submitter);
}

void HTMLFormElement::submit()
{
    submit(nullptr, false, true, NotSubmittedByJavaScript);
}

void HTMLFormElement::submitFromJavaScript()
{
    submit(nullptr, false, ScriptController::processingUserGesture(), SubmittedByJavaScript);
}

void HTMLFormElement::requestSubmit(ExceptionCode& ec)
{
	requestSubmit(nullptr, ec);
}

void HTMLFormElement::requestSubmit(HTMLElement* submitter, ExceptionCode& ec)
{
	ec = 0;

    // Update layout before processing form actions in case the style changes
    // the form or button relationships.
    document().updateLayoutIgnorePendingStylesheets();

    RefPtr<HTMLFormControlElement> control;
    if (submitter) {
        // https://html.spec.whatwg.org/multipage/forms.html#dom-form-requestsubmit
        if (!isHTMLFormControlElement(submitter)) {
        	ec = TypeError;
        	return;
        }
        control = toHTMLFormControlElement(submitter);
        if (!control->isSubmitButton()) {
        	ec = TypeError;
        	return;
        }
        if (control->form() != this) {
        	ec = NOT_FOUND_ERR;
        	return;
        }
    }

    submitIfPossible(nullptr, control.get(), SubmittedByJavaScript);
    return;
}

void HTMLFormElement::getTextFieldValues(StringPairVector& fieldNamesAndValues) const
{
    ASSERT_ARG(fieldNamesAndValues, fieldNamesAndValues.isEmpty());

    fieldNamesAndValues.reserveCapacity(m_associatedElements.size());
    for (unsigned i = 0; i < m_associatedElements.size(); ++i) {
    	const WeakPtr<HTMLElement>& weakElement = m_associatedElements[i];
        RefPtr<HTMLElement> element = weakElement.get();
        if (!element || !isHTMLInputElement(*element))
            continue;
        HTMLInputElement& input = toHTMLInputElement(*element);
        if (!input.isTextField())
            continue;
        fieldNamesAndValues.append(make_pair(input.name().string(), input.value()));
    }
}

RefPtr<HTMLFormControlElement> HTMLFormElement::findSubmitButton(HTMLFormControlElement* submitter, bool needButtonActivation)
{
    if (submitter)
        return submitter;
    if (!needButtonActivation)
        return nullptr;
    RefPtr<HTMLFormControlElement> firstSuccessfulSubmitButton;
    for (auto& associatedElement : m_associatedElements) {
        if (!isHTMLFormControlElement(*associatedElement))
            continue;
        auto& control = toHTMLFormControlElement(*associatedElement);
        if (control.isActivatedSubmit())
            return nullptr;
        if (!firstSuccessfulSubmitButton && control.isSuccessfulSubmitButton())
            firstSuccessfulSubmitButton = &control;
    }
    return firstSuccessfulSubmitButton;
}

void HTMLFormElement::submit(Event* event, bool activateSubmitButton, bool processingUserGesture, FormSubmissionTrigger trigger, HTMLFormControlElement* submitter)
{
    // The submitIfPossible function also does this check, but we need to do it here
	// too, since there are some code paths that bypass that function.
	RefPtr<FrameView> view = document().view();
	RefPtr<Frame> frame = document().frame();
    if (!view || !frame)
        return;

    if (m_isSubmittingOrPreparingForSubmission) {
        m_shouldSubmit = true;
        return;
    }

    m_isSubmittingOrPreparingForSubmission = true;
    m_wasUserSubmitted = processingUserGesture;

    auto firstSuccessfulSubmitButton = findSubmitButton(submitter, activateSubmitButton);
    if (firstSuccessfulSubmitButton)
        firstSuccessfulSubmitButton->setActivatedSubmit(true);

	Ref<HTMLFormElement> protect(*this); // Form submission can execute arbitary JavaScript.

    LockHistory lockHistory = processingUserGesture ? LockHistory::No : LockHistory::Yes;
    Ref<FormSubmission> formSubmission = FormSubmission::create(*this, submitter, m_attributes, event, lockHistory, trigger);

    FormRelAttributes relAttributes = parseFormRelAttributes(getAttribute(HTMLNames::relAttr));
    if (relAttributes.noopener || relAttributes.noreferrer || (!relAttributes.opener && document().settings().blankAnchorTargetImpliesNoOpenerEnabled() && isBlankTargetFrameName(formSubmission->target()) && !formSubmission->requestURL().protocolIsJavaScript()))
        formSubmission->setNewFrameOpenerPolicy(NewFrameOpenerPolicy::Suppress);
    if (relAttributes.noreferrer)
        formSubmission->setReferrerPolicy(ReferrerPolicy::NoReferrer);

    if (m_plannedFormSubmission)
		m_plannedFormSubmission->cancel();

	m_plannedFormSubmission = makeWeakPtr(formSubmission.get());

    if (RuntimeEnabledFeatures::dialogElementEnabled() && formSubmission->method() == FormSubmission::Method::DialogMethod)
        submitDialog(WTFMove(formSubmission));
    else
    	frame->loader()->submitForm(WTFMove(formSubmission));

    if (firstSuccessfulSubmitButton)
        firstSuccessfulSubmitButton->setActivatedSubmit(false);

    m_shouldSubmit = false;
    m_isSubmittingOrPreparingForSubmission = false;
}

// https://html.spec.whatwg.org/#submit-dialog
void HTMLFormElement::submitDialog(Ref<FormSubmission>&& formSubmission)
{
    // Let subject be the nearest ancestor dialog element of form, if any.
    RefPtr<HTMLDialogElement> dialog = ancestorsOfType<HTMLDialogElement>(*this).first();

    // If there isn't one, or if it does not have an open attribute, do nothing.
    if (!dialog || !dialog->isOpen())
        return;

    // Then, close the dialog subject. If there is a result, let that be the return value.
    dialog->close(formSubmission->returnValue());
}

void HTMLFormElement::reset()
{
    if (m_isInResetFunction)
        return;

    RefPtr<Frame> protectedFrame = document().frame();
    if (!protectedFrame)
        return;

    RefPtr<HTMLFormElement> protectedThis(this);

    SetForScope<bool> isInResetFunctionRestorer(m_isInResetFunction, true);

    RefPtr<Event> event = Event::create(eventNames().resetEvent, true, true);
    dispatchEvent(event);
    if (!event->defaultPrevented())
        resetAssociatedFormControlElements();
}

void HTMLFormElement::resetAssociatedFormControlElements()
{
    // Event handling can cause associated elements to be added or deleted while iterating
    // over this collection. Protect these elements until we are done notifying them of
    // the reset operation.
    Vector<RefPtr<HTMLFormControlElement>> associatedFormControlElements;
    associatedFormControlElements.reserveInitialCapacity(m_associatedElements.size());
    for (unsigned i = 0; i < m_associatedElements.size(); ++i) {
    	WeakPtr<HTMLElement>& weakElement = m_associatedElements[i];
        RefPtr<HTMLElement> element = weakElement.get();
        ASSERT(element);
        if (isHTMLFormControlElement(*element))
            associatedFormControlElements.uncheckedAppend(&toHTMLFormControlElement(*element));
    }

    for(int i = 0; i < associatedFormControlElements.size(); ++i)
    	associatedFormControlElements[i]->reset();
}

void HTMLFormElement::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason attributeModificationReason)
{
    if (name == actionAttr) {
        m_attributes.parseAction(newValue);
        if (!m_attributes.action().isEmpty()) {
            if (RefPtr<Frame> f = document().frame()) {
                if (auto* topFrame = f->tree()->top())
                	MixedContentChecker::checkFormForMixedContent(*topFrame, document().completeURL(m_attributes.action()));
            }
        }
    } else if (name == targetAttr)
        m_attributes.setTarget(newValue);
    else if (name == methodAttr)
        m_attributes.updateMethodType(newValue);
    else if (name == enctypeAttr)
        m_attributes.updateEncodingType(newValue);
    else if (name == accept_charsetAttr)
        m_attributes.setAcceptCharset(newValue);
    else if (name == autocompleteAttr) {
        if (!shouldAutocomplete())
            document().registerForPageCacheSuspensionCallbacks(*this);
        else
            document().unregisterForPageCacheSuspensionCallbacks(*this);
    } else if (name == relAttr) {
        if (m_relList)
            m_relList->associatedAttributeValueChanged();
    } else
    	HTMLElement::attributeChanged(name, oldValue, newValue, attributeModificationReason);
}

unsigned HTMLFormElement::formElementIndexWithFormAttribute(Element* element, unsigned rangeStart, unsigned rangeEnd)
{
    if (m_associatedElements.isEmpty())
        return 0;

    ASSERT(rangeStart <= rangeEnd);

    if (rangeStart == rangeEnd)
        return rangeStart;

    unsigned left = rangeStart;
    unsigned right = rangeEnd - 1;
    unsigned short position;

    // Does binary search on m_associatedElements in order to find the index
    // to be inserted.
    while (left != right) {
        unsigned middle = left + ((right - left) / 2);
        ASSERT(middle < m_associatedElementsBeforeIndex || middle >= m_associatedElementsAfterIndex);
        position = element->compareDocumentPosition(*m_associatedElements[middle]);
        if (position & DOCUMENT_POSITION_FOLLOWING)
            right = middle;
        else
            left = middle + 1;
    }
    
    ASSERT(left < m_associatedElementsBeforeIndex || left >= m_associatedElementsAfterIndex);
    position = element->compareDocumentPosition(*m_associatedElements[left]);
    if (position & DOCUMENT_POSITION_FOLLOWING)
        return left;
    return left + 1;
}

unsigned HTMLFormElement::formElementIndex(FormAssociatedElement* associatedElement)
{
    ASSERT(associatedElement);

    HTMLElement& associatedHTMLElement = associatedElement->asHTMLElement();

    // Treats separately the case where this element has the form attribute
    // for performance consideration.
    if (associatedHTMLElement.hasAttributeWithoutSynchronization(formAttr)) {
        unsigned short position = compareDocumentPosition(associatedHTMLElement);
        ASSERT(!(position & DOCUMENT_POSITION_DISCONNECTED));
        if (position & DOCUMENT_POSITION_PRECEDING) {
            ++m_associatedElementsBeforeIndex;
            ++m_associatedElementsAfterIndex;
            return HTMLFormElement::formElementIndexWithFormAttribute(&associatedHTMLElement, 0, m_associatedElementsBeforeIndex - 1);
        }
        if ((position & DOCUMENT_POSITION_FOLLOWING) && !(position & DOCUMENT_POSITION_CONTAINED_BY))
            return HTMLFormElement::formElementIndexWithFormAttribute(&associatedHTMLElement, m_associatedElementsAfterIndex, m_associatedElements.size());
    }

    // Check for the special case where this element is the very last thing in
    // the form's tree of children; we don't want to walk the entire tree in that
    // common case that occurs during parsing; instead we'll just return a value
    // that says "add this form element to the end of the array".
    if (ElementTraversal::next(&associatedHTMLElement, this)) {
        unsigned i = m_associatedElementsBeforeIndex;
        for (Element* element = this; element; element = ElementTraversal::next(element, this)) {
			if (element == &associatedHTMLElement) {
                ++m_associatedElementsAfterIndex;
                return i;
            }
            if (!element->isFormControlElement() && !element->hasTagName(objectTag))
                continue;
            if (!element->isHTMLElement() || toHTMLElement(element)->form() != this)
                continue;
			++i;
        }
    }
    return m_associatedElementsAfterIndex++;
}

void HTMLFormElement::registerFormElement(FormAssociatedElement* e)
{
    m_associatedElements.insert(formElementIndex(e), makeWeakPtr(e->asHTMLElement()));

    if (isHTMLFormControlElement(*e)) {
        HTMLFormControlElement& control = toHTMLFormControlElement(*e);
        if (control.isSuccessfulSubmitButton()) {
            if (!m_defaultButton)
                control.invalidateStyleForSubtree();
            else
                resetDefaultButton();
        }
    }
}

void HTMLFormElement::removeFormElement(FormAssociatedElement* e)
{
    unsigned index = m_associatedElements.find(&e->asHTMLElement());
    ASSERT(index < m_associatedElements.size());
    if (index < m_associatedElementsBeforeIndex)
        --m_associatedElementsBeforeIndex;
    if (index < m_associatedElementsAfterIndex)
        --m_associatedElementsAfterIndex;
    removeFromPastNamesMap(e);
    m_associatedElements.remove(index);

    if (e == m_defaultButton)
        resetDefaultButton();
}

void HTMLFormElement::registerInvalidAssociatedFormControl(const HTMLFormControlElement& formControlElement)
{
    ASSERT_WITH_MESSAGE(!is<HTMLFieldSetElement>(formControlElement), "FieldSet are never candidates for constraint validation.");
    ASSERT(static_cast<const Element&>(formControlElement).matchesInvalidPseudoClass());

    if (m_invalidAssociatedFormControls.computesEmpty())
    	invalidateStyleForSubtree();
    m_invalidAssociatedFormControls.add(const_cast<HTMLFormControlElement&>(formControlElement));
}

void HTMLFormElement::removeInvalidAssociatedFormControlIfNeeded(const HTMLFormControlElement& formControlElement)
{
    if (m_invalidAssociatedFormControls.remove(formControlElement)) {
        if (m_invalidAssociatedFormControls.computesEmpty())
        	invalidateStyleForSubtree();
    }
}

bool HTMLFormElement::isURLAttribute(const Attribute& attribute) const
{
    return attribute.name() == actionAttr || HTMLElement::isURLAttribute(attribute);
}

void HTMLFormElement::registerImgElement(HTMLImageElement* e)
{
    ASSERT(m_imageElements.find(e) == notFound);
    m_imageElements.append(makeWeakPtr(e));
}

void HTMLFormElement::removeImgElement(HTMLImageElement* e)
{
    removeFromPastNamesMap(e);
    bool removed = m_imageElements.removeFirst(e);
    ASSERT_UNUSED(removed, removed);
}

PassRefPtr<HTMLCollection> HTMLFormElement::elements()
{
    return ensureRareData().ensureNodeLists()->addCachedCollection<HTMLFormControlsCollection>(*this, FormControls);
}

String HTMLFormElement::name() const
{
    return getNameAttribute();
}

bool HTMLFormElement::noValidate() const
{
    return hasAttributeWithoutSynchronization(novalidateAttr);
}

// FIXME: This function should be removed because it does not do the same thing as the
// JavaScript binding for action, which treats action as a URL attribute. Last time I
// (Darin Adler) removed this, someone added it back, so I am leaving it in for now.
String HTMLFormElement::action() const
{
    return getAttribute(actionAttr);
}

void HTMLFormElement::setAction(const String &value)
{
    setAttribute(actionAttr, value);
}

void HTMLFormElement::setEnctype(const String &value)
{
    setAttribute(enctypeAttr, value);
}

String HTMLFormElement::method() const
{
    return FormSubmission::Attributes::methodString(m_attributes.method());
}

DOMTokenList& HTMLFormElement::relList()
{
    if (!m_relList) {
    	class IsSupportedTokenFunctionImpl final : public DOMTokenList::IsSupportedTokenFunction
		{
		public:
			virtual bool operator()(Document&, StringView token) const final
			{
				return equalIgnoringASCIICase(token, "noreferrer") || equalIgnoringASCIICase(token, "noopener") || equalIgnoringASCIICase(token, "opener");
			}
		};

        m_relList = adoptPtr(new DOMTokenList(*this, HTMLNames::relAttr, adoptPtr(new IsSupportedTokenFunctionImpl())));
    }
    return *m_relList;
}

void HTMLFormElement::setMethod(const String &value)
{
    setAttribute(methodAttr, value);
}

String HTMLFormElement::target() const
{
    return getAttribute(targetAttr);
}

String HTMLFormElement::effectiveTarget(const Event* event, HTMLFormControlElement* overrideSubmitter) const
{
    if (RefPtr<HTMLFormControlElement> submitter = overrideSubmitter ? overrideSubmitter : findSubmitter(event)) {
        const AtomicString& targetValue = submitter->attributeWithoutSynchronization(formtargetAttr);
        if (!targetValue.isNull())
            return targetValue;
    }

    auto targetValue = target();
    if (!targetValue.isNull())
        return targetValue;

    return document().baseTarget();
}

bool HTMLFormElement::wasUserSubmitted() const
{
    return m_wasUserSubmitted;
}

HTMLFormControlElement* HTMLFormElement::findSubmitter(const Event* event) const
{
    if (event && event->target()) {
        for (RefPtr<Node> node = event->target()->toNode(); node; node = node->parentNode()) {
            if (isHTMLFormControlElement(*node))
                return toHTMLFormControlElement(node.get());
        }
    }
    return nullptr;
}

HTMLFormControlElement* HTMLFormElement::defaultButton() const
{
    if (m_defaultButton)
        return m_defaultButton.get();
	for (unsigned i = 0; i < m_associatedElements.size(); ++i) {
		if (!isHTMLFormControlElement(*m_associatedElements[i]))
			continue;
		HTMLFormControlElement& control = toHTMLFormControlElement(*m_associatedElements[i]);
		if (control.isSuccessfulSubmitButton()) {
            m_defaultButton = makeWeakPtr(control);
            return &control;
        }
    }
    return 0;
}

void HTMLFormElement::resetDefaultButton()
{
	if (!m_defaultButton) {
		// Computing the default button is not cheap, we don't want to do it unless needed.
		// If there was no default button set, the only style to invalidate is the element
		// being added to the form. This is done explicitely in registerFormElement().
		return;
	}

    ScriptDisallowedScope::InMainThread scriptDisallowedScope;

    auto oldDefault = WTFMove(m_defaultButton);
	defaultButton();
	if (m_defaultButton != oldDefault) {
		if (oldDefault)
			oldDefault->invalidateStyleForSubtree();
		if (m_defaultButton)
			m_defaultButton->invalidateStyleForSubtree();
	}
}

bool HTMLFormElement::checkValidity()
{
    Vector<RefPtr<HTMLFormControlElement>> controls;
    return !checkInvalidControlsAndCollectUnhandled(controls);
}

bool HTMLFormElement::checkInvalidControlsAndCollectUnhandled(Vector<RefPtr<HTMLFormControlElement>>& unhandledInvalidControls)
{
    RefPtr<HTMLFormElement> protector(this);
    // Copy m_associatedElements because event handlers called from
    // HTMLFormControlElement::checkValidity() might change m_associatedElements.
    bool hasInvalidControls = false;
    for (auto& element : copyAssociatedElementsVector()) {
        if (element->form() == this && element->isFormControlElement()) {
            HTMLFormControlElement& control = static_cast<HTMLFormControlElement&>(element.get());
            if (!control.checkValidity(&unhandledInvalidControls) && control.form() == this)
                hasInvalidControls = true;
        }
    }
    return hasInvalidControls;
}

bool HTMLFormElement::reportValidity()
{
    return validateInteractively();
}

#if !ASSERT_DISABLED
void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem* item) const
{
    ASSERT(item);
    HTMLElement& element = item->asHTMLElement();
    ASSERT(element.form() == this);

    if (item->isFormAssociatedElement()) {
        ASSERT(m_associatedElements.find(&element) != notFound);
        return;
    }

    ASSERT(element.hasTagName(imgTag));
    ASSERT(m_imageElements.find(&downcast<HTMLImageElement>(element)) != notFound);
}
#endif

RefPtr<HTMLElement> HTMLFormElement::elementFromPastNamesMap(const AtomicString& pastName) const
{
    if (pastName.isEmpty() || m_pastNamesMap.isEmpty())
        return nullptr;
    auto weakElement = m_pastNamesMap.get(pastName);
    if (!weakElement)
        return nullptr;
    RefPtr<HTMLElement> element = weakElement.get();
#if !ASSERT_DISABLED
    assertItemCanBeInPastNamesMap(element->asFormNamedItem());
#endif
    return element.release();
}

void HTMLFormElement::addToPastNamesMap(FormNamedItem* item, const AtomicString& pastName)
{
#if !ASSERT_DISABLED
    assertItemCanBeInPastNamesMap(item);
#endif
    if (pastName.isEmpty())
        return;
    m_pastNamesMap.set(pastName.impl(), makeWeakPtr(item->asHTMLElement()));
}

void HTMLFormElement::removeFromPastNamesMap(FormNamedItem* item)
{
    ASSERT(item);
    if (m_pastNamesMap.isEmpty())
        return;

    class Functor
    {
    public:
    	Functor(HTMLElement& element) : element(element) { }
    	bool operator()(const HashMap<AtomicString, WeakPtr<HTMLElement>>::KeyValuePairType& iterator) const
    	{
    		return iterator.second == &element;
    	}
    private:
    	HTMLElement& element;
    };
    Functor functor(item->asHTMLElement());
    m_pastNamesMap.removeIf(functor);
}

bool HTMLFormElement::matchesValidPseudoClass() const
{
    return m_invalidAssociatedFormControls.computesEmpty();
}

bool HTMLFormElement::matchesInvalidPseudoClass() const
{
    return !matchesValidPseudoClass();
}

// FIXME: Use Ref<HTMLElement> for the function result since there are no non-HTML elements returned here.
Vector<RefPtr<Element>> HTMLFormElement::namedElements(const AtomicString& name)
{
    // http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem
	Vector<RefPtr<Element>> namedItems = elements()->namedItems(name);

    RefPtr<HTMLElement> elementFromPast = elementFromPastNamesMap(name);
    if (namedItems.size() == 1 && namedItems.first() != elementFromPast)
    	addToPastNamesMap(toHTMLElement(namedItems.first().get())->asFormNamedItem(), name);
    else if (elementFromPast && namedItems.isEmpty())
        namedItems.append(elementFromPast);

    return namedItems;
}

void HTMLFormElement::documentDidResumeFromPageCache()
{
    ASSERT(!shouldAutocomplete());

    RefPtr<HTMLFormElement> protectedThis(this);

    resetAssociatedFormControlElements();
}

void HTMLFormElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
{
    if (!shouldAutocomplete()) {
        oldDocument.unregisterForPageCacheSuspensionCallbacks(*this);
        newDocument.registerForPageCacheSuspensionCallbacks(*this);
    }

    HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
}

bool HTMLFormElement::shouldAutocomplete() const
{
    return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(autocompleteAttr), "off");
}

void HTMLFormElement::finishParsingChildren()
{
    HTMLElement::finishParsingChildren();
    document().formController().restoreControlStateIn(*this);
}

const Vector<WeakPtr<HTMLElement>>& HTMLFormElement::unsafeAssociatedElements() const
{
    ASSERT(!ScriptDisallowedScope::InMainThread::isScriptAllowed());
    return m_associatedElements;
}

Vector<Ref<FormAssociatedElement>> HTMLFormElement::copyAssociatedElementsVector() const
{
	Vector<Ref<FormAssociatedElement>> elements;
	elements.reserveCapacity(m_associatedElements.size());
	for(int i = 0; i < m_associatedElements.size(); ++i) {
    	const WeakPtr<HTMLElement>& weakElement = m_associatedElements[i];
        RefPtr<HTMLElement> element = weakElement.get();
        ASSERT(element);
        auto* associatedElement = element->asFormAssociatedElement();
        ASSERT(associatedElement);
		elements.uncheckedAppend(*associatedElement);
	}
    return elements;
}

HTMLFormElement* HTMLFormElement::findClosestFormAncestor(const Element& startElement)
{
    for (Element* element = startElement.parentElement(); element; element = element->parentElement()) {
        if (isHTMLFormElement(*element))
            return &toHTMLFormElement(*element);
    }
    return nullptr;
}

} // namespace
