| /* |
| * 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 "DOMFormData.h" |
| #include "DOMWindow.h" |
| #include "Document.h" |
| #include "ElementIterator.h" |
| #include "Event.h" |
| #include "EventNames.h" |
| #include "FormController.h" |
| #include "FormData.h" |
| #include "Frame.h" |
| #include "FrameLoader.h" |
| #include "FrameLoaderClient.h" |
| #include "HTMLFieldSetElement.h" |
| #include "HTMLFormControlsCollection.h" |
| #include "HTMLImageElement.h" |
| #include "HTMLInputElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLObjectElement.h" |
| #include "HTMLTableElement.h" |
| #include "NodeRareData.h" |
| #include "Page.h" |
| #include "RadioNodeList.h" |
| #include "RenderTextControl.h" |
| #include "ScriptDisallowedScope.h" |
| #include "Settings.h" |
| #include "UserGestureIndicator.h" |
| #include <limits> |
| #include <wtf/IsoMallocInlines.h> |
| #include <wtf/Ref.h> |
| #include <wtf/SetForScope.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLFormElement); |
| |
| using namespace HTMLNames; |
| |
| HTMLFormElement::HTMLFormElement(const QualifiedName& tagName, Document& document) |
| : HTMLElement(tagName, document) |
| { |
| ASSERT(hasTagName(formTag)); |
| } |
| |
| Ref<HTMLFormElement> HTMLFormElement::create(Document& document) |
| { |
| return adoptRef(*new HTMLFormElement(formTag, document)); |
| } |
| |
| Ref<HTMLFormElement> HTMLFormElement::create(const QualifiedName& tagName, Document& document) |
| { |
| return adoptRef(*new HTMLFormElement(tagName, document)); |
| } |
| |
| HTMLFormElement::~HTMLFormElement() |
| { |
| document().formController().willDeleteForm(*this); |
| if (!shouldAutocomplete()) |
| document().unregisterForDocumentSuspensionCallbacks(*this); |
| |
| m_defaultButton = nullptr; |
| for (auto& associatedElement : m_associatedElements) |
| associatedElement->formWillBeDestroyed(); |
| for (auto& imageElement : m_imageElements) |
| imageElement->m_form = nullptr; |
| } |
| |
| bool HTMLFormElement::formWouldHaveSecureSubmission(const String& url) |
| { |
| return document().completeURL(url).protocolIs("https"); |
| } |
| |
| bool HTMLFormElement::rendererIsNeeded(const RenderStyle& style) |
| { |
| if (!m_wasDemoted) |
| return HTMLElement::rendererIsNeeded(style); |
| |
| auto parent = parentNode(); |
| auto parentRenderer = parent->renderer(); |
| |
| if (!parentRenderer) |
| return false; |
| |
| // FIXME: Shouldn't we also check for table caption (see |formIsTablePart| below). |
| bool parentIsTableElementPart = (parentRenderer->isTable() && is<HTMLTableElement>(*parent)) |
| || (parentRenderer->isTableRow() && parent->hasTagName(trTag)) |
| || (parentRenderer->isTableSection() && parent->hasTagName(tbodyTag)) |
| || (parentRenderer->isRenderTableCol() && parent->hasTagName(colTag)) |
| || (parentRenderer->isTableCell() && parent->hasTagName(trTag)); |
| |
| if (!parentIsTableElementPart) |
| return true; |
| |
| DisplayType display = style.display(); |
| bool formIsTablePart = display == DisplayType::Table || display == DisplayType::InlineTable || display == DisplayType::TableRowGroup |
| || display == DisplayType::TableHeaderGroup || display == DisplayType::TableFooterGroup || display == DisplayType::TableRow |
| || display == DisplayType::TableColumnGroup || display == DisplayType::TableColumn || display == DisplayType::TableCell |
| || display == DisplayType::TableCaption; |
| |
| return formIsTablePart; |
| } |
| |
| Node::InsertedIntoAncestorResult HTMLFormElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
| { |
| HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
| if (insertionType.connectedToDocument) |
| document().didAssociateFormControl(*this); |
| return InsertedIntoAncestorResult::Done; |
| } |
| |
| void HTMLFormElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
| { |
| Node& root = traverseToRootNode(); // Do not rely on rootNode() because our IsInTreeScope is outdated. |
| Vector<FormAssociatedElement*> associatedElements(m_associatedElements); |
| for (auto& associatedElement : associatedElements) |
| associatedElement->formOwnerRemovedFromTree(root); |
| HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
| } |
| |
| void HTMLFormElement::handleLocalEvents(Event& event, EventInvokePhase phase) |
| { |
| if (event.eventPhase() != Event::CAPTURING_PHASE && is<Node>(event.target()) && event.target() != this && (event.type() == eventNames().submitEvent || event.type() == eventNames().resetEvent)) { |
| event.stopPropagation(); |
| return; |
| } |
| HTMLElement::handleLocalEvents(event, phase); |
| } |
| |
| unsigned HTMLFormElement::length() const |
| { |
| unsigned length = 0; |
| for (auto& associatedElement : m_associatedElements) { |
| if (associatedElement->isEnumeratable()) |
| ++length; |
| } |
| return length; |
| } |
| |
| HTMLElement* HTMLFormElement::item(unsigned index) |
| { |
| return elements()->item(index); |
| } |
| |
| Optional<Variant<RefPtr<RadioNodeList>, RefPtr<Element>>> HTMLFormElement::namedItem(const AtomString& name) |
| { |
| auto namedItems = namedElements(name); |
| |
| if (namedItems.isEmpty()) |
| return WTF::nullopt; |
| if (namedItems.size() == 1) |
| return Variant<RefPtr<RadioNodeList>, RefPtr<Element>> { RefPtr<Element> { WTFMove(namedItems[0]) } }; |
| |
| return Variant<RefPtr<RadioNodeList>, RefPtr<Element>> { RefPtr<RadioNodeList> { radioNodeList(name) } }; |
| } |
| |
| Vector<AtomString> HTMLFormElement::supportedPropertyNames() const |
| { |
| // FIXME: Should be implemented (only needed for enumeration with includeDontEnumProperties mode |
| // since this class is annotated with LegacyUnenumerableNamedProperties). |
| return { }; |
| } |
| |
| void HTMLFormElement::submitImplicitly(Event& event, bool fromImplicitSubmissionTrigger) |
| { |
| unsigned submissionTriggerCount = 0; |
| for (auto& formAssociatedElement : m_associatedElements) { |
| if (!is<HTMLFormControlElement>(*formAssociatedElement)) |
| continue; |
| HTMLFormControlElement& formElement = downcast<HTMLFormControlElement>(*formAssociatedElement); |
| if (formElement.isSuccessfulSubmitButton()) { |
| if (formElement.renderer()) { |
| formElement.dispatchSimulatedClick(&event); |
| return; |
| } |
| } else if (formElement.canTriggerImplicitSubmission()) |
| ++submissionTriggerCount; |
| } |
| |
| if (!submissionTriggerCount) |
| return; |
| |
| // Older iOS apps using WebViews expect the behavior of auto submitting multi-input forms. |
| if (fromImplicitSubmissionTrigger && (submissionTriggerCount == 1 || document().settings().allowMultiElementImplicitSubmission())) |
| prepareForSubmission(event); |
| } |
| |
| bool HTMLFormElement::validateInteractively() |
| { |
| for (auto& associatedElement : m_associatedElements) { |
| if (is<HTMLFormControlElement>(*associatedElement)) |
| downcast<HTMLFormControlElement>(*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. |
| |
| // Make sure layout is up-to-date in case we call isFocusable() (which |
| // has !renderer()->needsLayout() assertion). |
| ASSERT(!document().view() || !document().view()->needsLayout()); |
| |
| Ref<HTMLFormElement> protectedThis(*this); |
| |
| // Focus on the first focusable control and show a validation message. |
| for (auto& control : unhandledInvalidControls) { |
| if (control->isConnected() && control->isFocusable()) { |
| control->focusAndShowValidationMessage(); |
| break; |
| } |
| } |
| |
| // Warn about all of unfocusable controls. |
| if (document().frame()) { |
| for (auto& control : unhandledInvalidControls) { |
| if (control->isConnected() && control->isFocusable()) |
| continue; |
| String message = makeString("An invalid form control with name='", control->name(), "' is not focusable."); |
| document().addConsoleMessage(MessageSource::Rendering, MessageLevel::Error, message); |
| } |
| } |
| |
| return false; |
| } |
| |
| void HTMLFormElement::prepareForSubmission(Event& event) |
| { |
| if (!isConnected()) |
| return; |
| |
| 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) { |
| auto submitElement = findSubmitButton(&event); |
| if (submitElement && submitElement->formNoValidate()) |
| shouldValidate = false; |
| } |
| |
| // Interactive validation must be done before dispatching the submit event. |
| if (shouldValidate && !validateInteractively()) { |
| m_isSubmittingOrPreparingForSubmission = false; |
| return; |
| } |
| |
| auto targetFrame = frame->loader().findFrameForNavigation(effectiveTarget(&event), &document()); |
| if (!targetFrame) |
| targetFrame = frame.get(); |
| auto formState = FormState::create(*this, textFieldValues(), document(), NotSubmittedByJavaScript); |
| targetFrame->loader().client().dispatchWillSendSubmitEvent(WTFMove(formState)); |
| |
| auto protectedThis = makeRef(*this); |
| |
| auto submitEvent = Event::create(eventNames().submitEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes); |
| 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, true, NotSubmittedByJavaScript); |
| } |
| |
| void HTMLFormElement::submit() |
| { |
| submit(nullptr, false, true, NotSubmittedByJavaScript); |
| } |
| |
| void HTMLFormElement::submitFromJavaScript() |
| { |
| submit(nullptr, false, UserGestureIndicator::processingUserGesture(), SubmittedByJavaScript); |
| } |
| |
| StringPairVector HTMLFormElement::textFieldValues() const |
| { |
| StringPairVector result; |
| result.reserveInitialCapacity(m_associatedElements.size()); |
| for (auto& associatedElement : m_associatedElements) { |
| auto& element = associatedElement->asHTMLElement(); |
| if (!is<HTMLInputElement>(element)) |
| continue; |
| auto& input = downcast<HTMLInputElement>(element); |
| if (!input.isTextField()) |
| continue; |
| result.uncheckedAppend({ input.name().string(), input.value() }); |
| } |
| return result; |
| } |
| |
| void HTMLFormElement::submit(Event* event, bool activateSubmitButton, bool processingUserGesture, FormSubmissionTrigger formSubmissionTrigger) |
| { |
| 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; |
| |
| RefPtr<HTMLFormControlElement> firstSuccessfulSubmitButton; |
| bool needButtonActivation = activateSubmitButton; // do we need to activate a submit button? |
| |
| for (auto& associatedElement : m_associatedElements) { |
| if (!is<HTMLFormControlElement>(*associatedElement)) |
| continue; |
| if (needButtonActivation) { |
| HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*associatedElement); |
| if (control.isActivatedSubmit()) |
| needButtonActivation = false; |
| else if (!firstSuccessfulSubmitButton && control.isSuccessfulSubmitButton()) |
| firstSuccessfulSubmitButton = &control; |
| } |
| } |
| |
| if (needButtonActivation && firstSuccessfulSubmitButton) |
| firstSuccessfulSubmitButton->setActivatedSubmit(true); |
| |
| auto protectedThis = makeRef(*this); // Form submission can execute arbitary JavaScript. |
| |
| auto shouldLockHistory = processingUserGesture ? LockHistory::No : LockHistory::Yes; |
| frame->loader().submitForm(FormSubmission::create(*this, m_attributes, event, shouldLockHistory, formSubmissionTrigger)); |
| |
| if (needButtonActivation && firstSuccessfulSubmitButton) |
| firstSuccessfulSubmitButton->setActivatedSubmit(false); |
| |
| m_shouldSubmit = false; |
| m_isSubmittingOrPreparingForSubmission = false; |
| } |
| |
| void HTMLFormElement::reset() |
| { |
| if (m_isInResetFunction) |
| return; |
| |
| RefPtr<Frame> protectedFrame = document().frame(); |
| if (!protectedFrame) |
| return; |
| |
| Ref<HTMLFormElement> protectedThis(*this); |
| |
| SetForScope<bool> isInResetFunctionRestorer(m_isInResetFunction, true); |
| |
| auto event = Event::create(eventNames().resetEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes); |
| 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<Ref<HTMLFormControlElement>> associatedFormControlElements; |
| associatedFormControlElements.reserveInitialCapacity(m_associatedElements.size()); |
| for (auto* element : m_associatedElements) { |
| if (is<HTMLFormControlElement>(element)) |
| associatedFormControlElements.uncheckedAppend(*downcast<HTMLFormControlElement>(element)); |
| } |
| |
| for (auto& associatedFormControlElement : associatedFormControlElements) |
| associatedFormControlElement->reset(); |
| } |
| |
| #if ENABLE(IOS_AUTOCORRECT_AND_AUTOCAPITALIZE) |
| |
| // FIXME: We should look to share this code with class HTMLFormControlElement instead of duplicating the logic. |
| |
| bool HTMLFormElement::shouldAutocorrect() const |
| { |
| const AtomString& autocorrectValue = attributeWithoutSynchronization(autocorrectAttr); |
| if (!autocorrectValue.isEmpty()) |
| return !equalLettersIgnoringASCIICase(autocorrectValue, "off"); |
| if (RefPtr<HTMLFormElement> form = this->form()) |
| return form->shouldAutocorrect(); |
| return true; |
| } |
| |
| #endif |
| |
| void HTMLFormElement::parseAttribute(const QualifiedName& name, const AtomString& value) |
| { |
| if (name == actionAttr) { |
| m_attributes.parseAction(value); |
| |
| if (!m_attributes.action().isEmpty()) { |
| if (RefPtr<Frame> f = document().frame()) { |
| Frame& topFrame = f->tree().top(); |
| topFrame.loader().mixedContentChecker().checkFormForMixedContent(topFrame.document()->securityOrigin(), document().completeURL(m_attributes.action())); |
| } |
| } |
| } else if (name == targetAttr) |
| m_attributes.setTarget(value); |
| else if (name == methodAttr) |
| m_attributes.updateMethodType(value); |
| else if (name == enctypeAttr) |
| m_attributes.updateEncodingType(value); |
| else if (name == accept_charsetAttr) |
| m_attributes.setAcceptCharset(value); |
| else if (name == autocompleteAttr) { |
| if (!shouldAutocomplete()) |
| document().registerForDocumentSuspensionCallbacks(*this); |
| else |
| document().unregisterForDocumentSuspensionCallbacks(*this); |
| } else |
| HTMLElement::parseAttribute(name, value); |
| } |
| |
| 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]->asHTMLElement()); |
| if (position & DOCUMENT_POSITION_FOLLOWING) |
| right = middle; |
| else |
| left = middle + 1; |
| } |
| |
| ASSERT(left < m_associatedElementsBeforeIndex || left >= m_associatedElementsAfterIndex); |
| position = element->compareDocumentPosition(m_associatedElements[left]->asHTMLElement()); |
| 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) && associatedHTMLElement.isConnected()) { |
| unsigned short position = compareDocumentPosition(associatedHTMLElement); |
| ASSERT_WITH_SECURITY_IMPLICATION(!(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()); |
| } |
| |
| unsigned currentAssociatedElementsAfterIndex = m_associatedElementsAfterIndex; |
| ++m_associatedElementsAfterIndex; |
| |
| if (!associatedHTMLElement.isDescendantOf(*this)) |
| return currentAssociatedElementsAfterIndex; |
| |
| // 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". |
| auto descendants = descendantsOfType<HTMLElement>(*this); |
| auto it = descendants.beginAt(associatedHTMLElement); |
| auto end = descendants.end(); |
| if (++it == end) |
| return currentAssociatedElementsAfterIndex; |
| |
| unsigned i = m_associatedElementsBeforeIndex; |
| for (auto& element : descendants) { |
| if (&element == &associatedHTMLElement) |
| return i; |
| if (!is<HTMLFormControlElement>(element) && !is<HTMLObjectElement>(element)) |
| continue; |
| if (element.form() != this) |
| continue; |
| ++i; |
| } |
| return currentAssociatedElementsAfterIndex; |
| } |
| |
| void HTMLFormElement::registerFormElement(FormAssociatedElement* e) |
| { |
| m_associatedElements.insert(formElementIndex(e), e); |
| |
| if (is<HTMLFormControlElement>(e)) { |
| HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*e); |
| if (control.isSuccessfulSubmitButton()) { |
| if (!m_defaultButton) |
| control.invalidateStyleForSubtree(); |
| else |
| resetDefaultButton(); |
| } |
| } |
| } |
| |
| void HTMLFormElement::removeFormElement(FormAssociatedElement* e) |
| { |
| unsigned index = m_associatedElements.find(e); |
| ASSERT_WITH_SECURITY_IMPLICATION(index < m_associatedElements.size()); |
| if (index < m_associatedElementsBeforeIndex) |
| --m_associatedElementsBeforeIndex; |
| if (index < m_associatedElementsAfterIndex) |
| --m_associatedElementsAfterIndex; |
| removeFromPastNamesMap(e); |
| m_associatedElements.remove(index); |
| |
| if (auto* nodeLists = this->nodeLists()) |
| nodeLists->invalidateCaches(); |
| |
| 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); |
| } |
| |
| Ref<HTMLFormControlsCollection> HTMLFormElement::elements() |
| { |
| return ensureRareData().ensureNodeLists().addCachedCollection<HTMLFormControlsCollection>(*this, FormControls); |
| } |
| |
| Ref<HTMLCollection> HTMLFormElement::elementsForNativeBindings() |
| { |
| return elements(); |
| } |
| |
| 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 attributeWithoutSynchronization(actionAttr); |
| } |
| |
| void HTMLFormElement::setAction(const String &value) |
| { |
| setAttributeWithoutSynchronization(actionAttr, value); |
| } |
| |
| void HTMLFormElement::setEnctype(const String &value) |
| { |
| setAttributeWithoutSynchronization(enctypeAttr, value); |
| } |
| |
| String HTMLFormElement::method() const |
| { |
| return FormSubmission::Attributes::methodString(m_attributes.method()); |
| } |
| |
| void HTMLFormElement::setMethod(const String &value) |
| { |
| setAttributeWithoutSynchronization(methodAttr, value); |
| } |
| |
| String HTMLFormElement::target() const |
| { |
| return attributeWithoutSynchronization(targetAttr); |
| } |
| |
| String HTMLFormElement::effectiveTarget(const Event* event) const |
| { |
| if (auto* submitButton = findSubmitButton(event)) { |
| auto targetValue = submitButton->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::findSubmitButton(const Event* event) const |
| { |
| if (!event || !is<Node>(event->target())) |
| return nullptr; |
| auto& node = downcast<Node>(*event->target()); |
| auto* element = is<Element>(node) ? &downcast<Element>(node) : node.parentElement(); |
| return element ? lineageOfType<HTMLFormControlElement>(*element).first() : nullptr; |
| } |
| |
| HTMLFormControlElement* HTMLFormElement::defaultButton() const |
| { |
| if (m_defaultButton) |
| return m_defaultButton.get(); |
| for (auto& associatedElement : m_associatedElements) { |
| if (!is<HTMLFormControlElement>(*associatedElement)) |
| continue; |
| HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*associatedElement); |
| if (control.isSuccessfulSubmitButton()) { |
| m_defaultButton = makeWeakPtr(control); |
| return &control; |
| } |
| } |
| return nullptr; |
| } |
| |
| 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) |
| { |
| Ref<HTMLFormElement> protectedThis(*this); |
| // Copy m_associatedElements because event handlers called from |
| // HTMLFormControlElement::checkValidity() might change m_associatedElements. |
| Vector<RefPtr<FormAssociatedElement>> elements; |
| elements.reserveCapacity(m_associatedElements.size()); |
| for (auto& associatedElement : m_associatedElements) |
| elements.append(associatedElement); |
| bool hasInvalidControls = false; |
| for (auto& element : elements) { |
| if (element->form() == this && is<HTMLFormControlElement>(*element)) { |
| HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*element); |
| if (!control.checkValidity(&unhandledInvalidControls) && control.form() == this) |
| hasInvalidControls = true; |
| } |
| } |
| return hasInvalidControls; |
| } |
| |
| bool HTMLFormElement::reportValidity() |
| { |
| Ref<HTMLFormElement> protectedThis(*this); |
| |
| // Update layout before processing form actions in case the style changes |
| // the Form or button relationships. |
| document().updateLayoutIgnorePendingStylesheets(); |
| |
| return validateInteractively(); |
| } |
| |
| #ifndef NDEBUG |
| void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem* item) const |
| { |
| ASSERT_WITH_SECURITY_IMPLICATION(item); |
| HTMLElement& element = item->asHTMLElement(); |
| ASSERT_WITH_SECURITY_IMPLICATION(element.form() == this); |
| |
| if (item->isFormAssociatedElement()) { |
| ASSERT_WITH_SECURITY_IMPLICATION(m_associatedElements.find(static_cast<FormAssociatedElement*>(item)) != notFound); |
| return; |
| } |
| |
| ASSERT_WITH_SECURITY_IMPLICATION(element.hasTagName(imgTag)); |
| ASSERT_WITH_SECURITY_IMPLICATION(m_imageElements.find(&downcast<HTMLImageElement>(element)) != notFound); |
| } |
| #else |
| inline void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem*) const |
| { |
| } |
| #endif |
| |
| RefPtr<HTMLElement> HTMLFormElement::elementFromPastNamesMap(const AtomString& pastName) const |
| { |
| if (pastName.isEmpty() || !m_pastNamesMap) |
| return nullptr; |
| FormNamedItem* item = m_pastNamesMap->get(pastName.impl()); |
| if (!item) |
| return nullptr; |
| assertItemCanBeInPastNamesMap(item); |
| return &item->asHTMLElement(); |
| } |
| |
| void HTMLFormElement::addToPastNamesMap(FormNamedItem* item, const AtomString& pastName) |
| { |
| assertItemCanBeInPastNamesMap(item); |
| if (pastName.isEmpty()) |
| return; |
| if (!m_pastNamesMap) |
| m_pastNamesMap = makeUnique<PastNamesMap>(); |
| m_pastNamesMap->set(pastName.impl(), item); |
| } |
| |
| void HTMLFormElement::removeFromPastNamesMap(FormNamedItem* item) |
| { |
| ASSERT(item); |
| if (!m_pastNamesMap) |
| return; |
| |
| for (auto& pastName : m_pastNamesMap->values()) { |
| if (pastName == item) |
| pastName = nullptr; // Keep looping. Single element can have multiple names. |
| } |
| } |
| |
| 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<Ref<Element>> HTMLFormElement::namedElements(const AtomString& name) |
| { |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem |
| Vector<Ref<Element>> namedItems = elements()->namedItems(name); |
| |
| auto elementFromPast = elementFromPastNamesMap(name); |
| if (namedItems.size() == 1 && namedItems.first().ptr() != elementFromPast) |
| addToPastNamesMap(downcast<HTMLElement>(namedItems.first().get()).asFormNamedItem(), name); |
| else if (elementFromPast && namedItems.isEmpty()) |
| namedItems.append(*elementFromPast); |
| |
| return namedItems; |
| } |
| |
| void HTMLFormElement::resumeFromDocumentSuspension() |
| { |
| ASSERT(!shouldAutocomplete()); |
| |
| document().postTask([formElement = makeRef(*this)] (ScriptExecutionContext&) { |
| formElement->resetAssociatedFormControlElements(); |
| }); |
| } |
| |
| void HTMLFormElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument) |
| { |
| if (!shouldAutocomplete()) { |
| oldDocument.unregisterForDocumentSuspensionCallbacks(*this); |
| newDocument.registerForDocumentSuspensionCallbacks(*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<FormAssociatedElement*>& HTMLFormElement::unsafeAssociatedElements() const |
| { |
| ASSERT(ScriptDisallowedScope::InMainThread::hasDisallowedScope()); |
| return m_associatedElements; |
| } |
| |
| Vector<Ref<FormAssociatedElement>> HTMLFormElement::copyAssociatedElementsVector() const |
| { |
| return WTF::map(m_associatedElements, [] (auto* rawElement) { |
| return Ref<FormAssociatedElement>(*rawElement); |
| }); |
| } |
| |
| void HTMLFormElement::copyNonAttributePropertiesFromElement(const Element& source) |
| { |
| m_wasDemoted = static_cast<const HTMLFormElement&>(source).m_wasDemoted; |
| HTMLElement::copyNonAttributePropertiesFromElement(source); |
| } |
| |
| HTMLFormElement* HTMLFormElement::findClosestFormAncestor(const Element& startElement) |
| { |
| return const_cast<HTMLFormElement*>(ancestorsOfType<HTMLFormElement>(startElement).first()); |
| } |
| |
| void HTMLFormElement::setAutocomplete(const AtomString& value) |
| { |
| setAttributeWithoutSynchronization(autocompleteAttr, value); |
| } |
| |
| const AtomString& HTMLFormElement::autocomplete() const |
| { |
| static NeverDestroyed<AtomString> on("on", AtomString::ConstructFromLiteral); |
| static NeverDestroyed<AtomString> off("off", AtomString::ConstructFromLiteral); |
| |
| return equalIgnoringASCIICase(attributeWithoutSynchronization(autocompleteAttr), "off") ? off : on; |
| } |
| |
| } // namespace |