diff options
Diffstat (limited to 'Source/WebCore/html/HTMLFormElement.cpp')
-rw-r--r-- | Source/WebCore/html/HTMLFormElement.cpp | 543 |
1 files changed, 338 insertions, 205 deletions
diff --git a/Source/WebCore/html/HTMLFormElement.cpp b/Source/WebCore/html/HTMLFormElement.cpp index 0c301ebe1..5ae89af47 100644 --- a/Source/WebCore/html/HTMLFormElement.cpp +++ b/Source/WebCore/html/HTMLFormElement.cpp @@ -2,7 +2,7 @@ * 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, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * 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 @@ -25,7 +25,9 @@ #include "config.h" #include "HTMLFormElement.h" -#include "Attribute.h" +#include "AutocompleteErrorEvent.h" +#include "DOMFormData.h" +#include "DOMWindow.h" #include "Document.h" #include "ElementIterator.h" #include "Event.h" @@ -35,17 +37,22 @@ #include "Frame.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" -#include "HTMLCollection.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 "ScriptController.h" #include "Settings.h" #include <limits> #include <wtf/Ref.h> +#include <wtf/SetForScope.h> namespace WebCore { @@ -53,37 +60,34 @@ using namespace HTMLNames; HTMLFormElement::HTMLFormElement(const QualifiedName& tagName, Document& document) : HTMLElement(tagName, document) - , m_associatedElementsBeforeIndex(0) - , m_associatedElementsAfterIndex(0) - , m_wasUserSubmitted(false) - , m_isSubmittingOrPreparingForSubmission(false) - , m_shouldSubmit(false) - , m_isInResetFunction(false) - , m_wasDemoted(false) +#if ENABLE(REQUEST_AUTOCOMPLETE) + , m_requestAutocompletetimer(*this, &HTMLFormElement::requestAutocompleteTimerFired) +#endif { ASSERT(hasTagName(formTag)); } -PassRefPtr<HTMLFormElement> HTMLFormElement::create(Document& document) +Ref<HTMLFormElement> HTMLFormElement::create(Document& document) { - return adoptRef(new HTMLFormElement(formTag, document)); + return adoptRef(*new HTMLFormElement(formTag, document)); } -PassRefPtr<HTMLFormElement> HTMLFormElement::create(const QualifiedName& tagName, Document& document) +Ref<HTMLFormElement> HTMLFormElement::create(const QualifiedName& tagName, Document& document) { - return adoptRef(new HTMLFormElement(tagName, document)); + return adoptRef(*new HTMLFormElement(tagName, document)); } HTMLFormElement::~HTMLFormElement() { document().formController().willDeleteForm(this); if (!shouldAutocomplete()) - document().unregisterForPageCacheSuspensionCallbacks(this); + document().unregisterForDocumentSuspensionCallbacks(this); - for (unsigned i = 0; i < m_associatedElements.size(); ++i) - m_associatedElements[i]->formWillBeDestroyed(); - for (unsigned i = 0; i < m_imageElements.size(); ++i) - m_imageElements[i]->m_form = 0; + 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) @@ -103,7 +107,7 @@ bool HTMLFormElement::rendererIsNeeded(const RenderStyle& style) return false; // FIXME: Shouldn't we also check for table caption (see |formIsTablePart| below). - bool parentIsTableElementPart = (parentRenderer->isTable() && isHTMLTableElement(parent)) + bool parentIsTableElementPart = (parentRenderer->isTable() && is<HTMLTableElement>(*parent)) || (parentRenderer->isTableRow() && parent->hasTagName(trTag)) || (parentRenderer->isTableSection() && parent->hasTagName(tbodyTag)) || (parentRenderer->isRenderTableCol() && parent->hasTagName(colTag)) @@ -124,7 +128,7 @@ bool HTMLFormElement::rendererIsNeeded(const RenderStyle& style) Node::InsertionNotificationRequest HTMLFormElement::insertedInto(ContainerNode& insertionPoint) { HTMLElement::insertedInto(insertionPoint); - if (insertionPoint.inDocument()) + if (insertionPoint.isConnected()) document().didAssociateFormControl(this); return InsertionDone; } @@ -141,8 +145,8 @@ void HTMLFormElement::removedFrom(ContainerNode& insertionPoint) { Node* root = findRoot(this); Vector<FormAssociatedElement*> associatedElements(m_associatedElements); - for (unsigned i = 0; i < associatedElements.size(); ++i) - associatedElements[i]->formRemovedFromTree(root); + for (auto& associatedElement : associatedElements) + associatedElement->formRemovedFromTree(root); HTMLElement::removedFrom(insertionPoint); } @@ -159,31 +163,50 @@ void HTMLFormElement::handleLocalEvents(Event& event) unsigned HTMLFormElement::length() const { unsigned len = 0; - for (unsigned i = 0; i < m_associatedElements.size(); ++i) - if (m_associatedElements[i]->isEnumeratable()) + for (auto& associatedElement : m_associatedElements) { + if (associatedElement->isEnumeratable()) ++len; + } return len; } -Node* HTMLFormElement::item(unsigned index) +HTMLElement* HTMLFormElement::item(unsigned index) { return elements()->item(index); } -void HTMLFormElement::submitImplicitly(Event* event, bool fromImplicitSubmissionTrigger) +std::optional<Variant<RefPtr<RadioNodeList>, RefPtr<Element>>> HTMLFormElement::namedItem(const AtomicString& name) +{ + auto namedItems = namedElements(name); + + if (namedItems.isEmpty()) + return std::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<AtomicString> 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 (unsigned i = 0; i < m_associatedElements.size(); ++i) { - FormAssociatedElement* formAssociatedElement = m_associatedElements[i]; - if (!formAssociatedElement->isFormControlElement()) + for (auto& formAssociatedElement : m_associatedElements) { + if (!is<HTMLFormControlElement>(*formAssociatedElement)) continue; - HTMLFormControlElement* formElement = toHTMLFormControlElement(formAssociatedElement); - if (formElement->isSuccessfulSubmitButton()) { - if (formElement->renderer()) { - formElement->dispatchSimulatedClick(event); + HTMLFormControlElement& formElement = downcast<HTMLFormControlElement>(*formAssociatedElement); + if (formElement.isSuccessfulSubmitButton()) { + if (formElement.renderer()) { + formElement.dispatchSimulatedClick(&event); return; } - } else if (formElement->canTriggerImplicitSubmission()) + } else if (formElement.canTriggerImplicitSubmission()) ++submissionTriggerCount; } @@ -191,36 +214,27 @@ void HTMLFormElement::submitImplicitly(Event* event, bool fromImplicitSubmission return; // Older iOS apps using WebViews expect the behavior of auto submitting multi-input forms. - Settings* settings = document().settings(); - if (fromImplicitSubmissionTrigger && (submissionTriggerCount == 1 || (settings && settings->allowMultiElementImplicitSubmission()))) + if (fromImplicitSubmissionTrigger && (submissionTriggerCount == 1 || document().settings().allowMultiElementImplicitSubmission())) prepareForSubmission(event); } -static inline HTMLFormControlElement* submitElementFromEvent(const Event* event) +static inline HTMLFormControlElement* submitElementFromEvent(const Event& event) { - for (Node* node = event->target()->toNode(); node; node = node->parentNode()) { - if (node->isElementNode() && toElement(node)->isFormControlElement()) - return toHTMLFormControlElement(node); + for (Node* node = event.target()->toNode(); node; node = node->parentNode()) { + if (is<HTMLFormControlElement>(*node)) + return downcast<HTMLFormControlElement>(node); } - return 0; + return nullptr; } -bool HTMLFormElement::validateInteractively(Event* event) +bool HTMLFormElement::validateInteractively() { - ASSERT(event); - if (!document().page() || !document().page()->settings().interactiveFormValidationEnabled() || noValidate()) - return true; - - HTMLFormControlElement* submitElement = submitElementFromEvent(event); - if (submitElement && submitElement->formNoValidate()) - return true; - - for (unsigned i = 0; i < m_associatedElements.size(); ++i) { - if (m_associatedElements[i]->isFormControlElement()) - toHTMLFormControlElement(m_associatedElements[i])->hideVisibleValidationMessage(); + for (auto& associatedElement : m_associatedElements) { + if (is<HTMLFormControlElement>(*associatedElement)) + downcast<HTMLFormControlElement>(*associatedElement).hideVisibleValidationMessage(); } - Vector<RefPtr<FormAssociatedElement>> unhandledInvalidControls; + Vector<RefPtr<HTMLFormControlElement>> unhandledInvalidControls; if (!checkInvalidControlsAndCollectUnhandled(unhandledInvalidControls)) return true; // Because the form has invalid controls, we abort the form submission and @@ -230,66 +244,63 @@ bool HTMLFormElement::validateInteractively(Event* event) // has !renderer()->needsLayout() assertion. document().updateLayoutIgnorePendingStylesheets(); - Ref<HTMLFormElement> protect(*this); + Ref<HTMLFormElement> protectedThis(*this); // Focus on the first focusable control and show a validation message. - for (unsigned i = 0; i < unhandledInvalidControls.size(); ++i) { - HTMLElement& element = unhandledInvalidControls[i]->asHTMLElement(); - if (element.inDocument() && element.isFocusable()) { - element.scrollIntoViewIfNeeded(false); - element.focus(); - if (element.isFormControlElement()) - toHTMLFormControlElement(element).updateVisibleValidationMessage(); + for (auto& control : unhandledInvalidControls) { + if (control->isConnected() && control->isFocusable()) { + control->focusAndShowValidationMessage(); break; } } // Warn about all of unfocusable controls. if (document().frame()) { - for (unsigned i = 0; i < unhandledInvalidControls.size(); ++i) { - FormAssociatedElement& control = *unhandledInvalidControls[i]; - HTMLElement& element = control.asHTMLElement(); - if (element.inDocument() && element.isFocusable()) + for (auto& control : unhandledInvalidControls) { + if (control->isConnected() && control->isFocusable()) continue; - String message("An invalid form control with name='%name' is not focusable."); - message.replace("%name", control.name()); - document().addConsoleMessage(RenderingMessageSource, ErrorMessageLevel, message); + String message = makeString("An invalid form control with name='", control->name(), "' is not focusable."); + document().addConsoleMessage(MessageSource::Rendering, MessageLevel::Error, message); } } return false; } -bool HTMLFormElement::prepareForSubmission(Event* event) +void HTMLFormElement::prepareForSubmission(Event& event) { Frame* frame = document().frame(); if (m_isSubmittingOrPreparingForSubmission || !frame) - return m_isSubmittingOrPreparingForSubmission; + return; m_isSubmittingOrPreparingForSubmission = true; m_shouldSubmit = false; + bool shouldValidate = document().page() && document().page()->settings().interactiveFormValidationEnabled() && !noValidate(); + + HTMLFormControlElement* submitElement = submitElementFromEvent(event); + if (submitElement && submitElement->formNoValidate()) + shouldValidate = false; + // Interactive validation must be done before dispatching the submit event. - if (!validateInteractively(event)) { + if (shouldValidate && !validateInteractively()) { m_isSubmittingOrPreparingForSubmission = false; - return false; + return; } - StringPairVector controlNamesAndValues; - getTextFieldValues(controlNamesAndValues); - RefPtr<FormState> formState = FormState::create(this, controlNamesAndValues, &document(), NotSubmittedByJavaScript); - frame->loader().client().dispatchWillSendSubmitEvent(formState.release()); + auto formState = FormState::create(*this, textFieldValues(), document(), NotSubmittedByJavaScript); + frame->loader().client().dispatchWillSendSubmitEvent(WTFMove(formState)); + + Ref<HTMLFormElement> protectedThis(*this); - Ref<HTMLFormElement> protect(*this); + // Event handling can result in m_shouldSubmit becoming true, regardless of dispatchEvent() return value. if (dispatchEvent(Event::create(eventNames().submitEvent, true, true))) m_shouldSubmit = true; m_isSubmittingOrPreparingForSubmission = false; if (m_shouldSubmit) - submit(event, true, true, NotSubmittedByJavaScript); - - return m_shouldSubmit; + submit(&event, true, true, NotSubmittedByJavaScript); } void HTMLFormElement::submit() @@ -302,21 +313,20 @@ void HTMLFormElement::submitFromJavaScript() submit(0, false, ScriptController::processingUserGesture(), SubmittedByJavaScript); } -void HTMLFormElement::getTextFieldValues(StringPairVector& fieldNamesAndValues) const +StringPairVector HTMLFormElement::textFieldValues() const { - ASSERT_ARG(fieldNamesAndValues, fieldNamesAndValues.isEmpty()); - - fieldNamesAndValues.reserveCapacity(m_associatedElements.size()); - for (unsigned i = 0; i < m_associatedElements.size(); ++i) { - FormAssociatedElement& control = *m_associatedElements[i]; - HTMLElement& element = control.asHTMLElement(); - if (!isHTMLInputElement(element)) + StringPairVector result; + result.reserveInitialCapacity(m_associatedElements.size()); + for (auto& associatedElement : m_associatedElements) { + auto& element = associatedElement->asHTMLElement(); + if (!is<HTMLInputElement>(element)) continue; - HTMLInputElement& input = toHTMLInputElement(element); + auto& input = downcast<HTMLInputElement>(element); if (!input.isTextField()) continue; - fieldNamesAndValues.append(std::make_pair(input.name().string(), input.value())); + result.uncheckedAppend({ input.name().string(), input.value() }); } + return result; } void HTMLFormElement::submit(Event* event, bool activateSubmitButton, bool processingUserGesture, FormSubmissionTrigger formSubmissionTrigger) @@ -337,25 +347,25 @@ void HTMLFormElement::submit(Event* event, bool activateSubmitButton, bool proce RefPtr<HTMLFormControlElement> firstSuccessfulSubmitButton; bool needButtonActivation = activateSubmitButton; // do we need to activate a submit button? - for (unsigned i = 0; i < m_associatedElements.size(); ++i) { - FormAssociatedElement* associatedElement = m_associatedElements[i]; - if (!associatedElement->isFormControlElement()) + for (auto& associatedElement : m_associatedElements) { + if (!is<HTMLFormControlElement>(*associatedElement)) continue; if (needButtonActivation) { - HTMLFormControlElement* control = toHTMLFormControlElement(associatedElement); - if (control->isActivatedSubmit()) + HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*associatedElement); + if (control.isActivatedSubmit()) needButtonActivation = false; - else if (firstSuccessfulSubmitButton == 0 && control->isSuccessfulSubmitButton()) - firstSuccessfulSubmitButton = control; + else if (!firstSuccessfulSubmitButton && control.isSuccessfulSubmitButton()) + firstSuccessfulSubmitButton = &control; } } if (needButtonActivation && firstSuccessfulSubmitButton) firstSuccessfulSubmitButton->setActivatedSubmit(true); - bool lockHistory = !processingUserGesture; - Ref<HTMLFormElement> protect(*this); // Form submission can execute arbitary JavaScript. - frame->loader().submitForm(FormSubmission::create(this, m_attributes, event, lockHistory, formSubmissionTrigger)); + 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); @@ -370,60 +380,116 @@ void HTMLFormElement::reset() if (m_isInResetFunction || !frame) return; - m_isInResetFunction = true; + Ref<HTMLFormElement> protectedThis(*this); - if (!dispatchEvent(Event::create(eventNames().resetEvent, true, true))) { - m_isInResetFunction = false; + SetForScope<bool> isInResetFunctionRestorer(m_isInResetFunction, true); + + if (!dispatchEvent(Event::create(eventNames().resetEvent, true, true))) return; - } - for (unsigned i = 0; i < m_associatedElements.size(); ++i) { - if (m_associatedElements[i]->isFormControlElement()) - toHTMLFormControlElement(m_associatedElements[i])->reset(); - } + resetAssociatedFormControlElements(); +} - m_isInResetFunction = false; +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 these methods with class HTMLFormControlElement instead of duplicating them. -bool HTMLFormElement::autocorrect() const +// FIXME: We should look to share this code with class HTMLFormControlElement instead of duplicating the logic. + +bool HTMLFormElement::shouldAutocorrect() const { - const AtomicString& autocorrectValue = fastGetAttribute(autocorrectAttr); + const AtomicString& autocorrectValue = attributeWithoutSynchronization(autocorrectAttr); if (!autocorrectValue.isEmpty()) - return !equalIgnoringCase(autocorrectValue, "off"); + return !equalLettersIgnoringASCIICase(autocorrectValue, "off"); if (HTMLFormElement* form = this->form()) - return form->autocorrect(); + return form->shouldAutocorrect(); return true; } -void HTMLFormElement::setAutocorrect(bool autocorrect) -{ - setAttribute(autocorrectAttr, autocorrect ? AtomicString("on", AtomicString::ConstructFromLiteral) : AtomicString("off", AtomicString::ConstructFromLiteral)); -} +#endif -WebAutocapitalizeType HTMLFormElement::autocapitalizeType() const -{ - return autocapitalizeTypeForAttributeValue(fastGetAttribute(autocapitalizeAttr)); -} +#if ENABLE(REQUEST_AUTOCOMPLETE) -const AtomicString& HTMLFormElement::autocapitalize() const +void HTMLFormElement::requestAutocomplete() { - return stringForAutocapitalizeType(autocapitalizeType()); + Frame* frame = document().frame(); + if (!frame) + return; + + if (!shouldAutocomplete() || !ScriptController::processingUserGesture()) { + finishRequestAutocomplete(AutocompleteResult::ErrorDisabled); + return; + } + + StringPairVector controlNamesAndValues; + getTextFieldValues(controlNamesAndValues); + + auto formState = FormState::create(this, controlNamesAndValues, &document(), SubmittedByJavaScript); + frame->loader().client().didRequestAutocomplete(WTFMove(formState)); +} + +void HTMLFormElement::finishRequestAutocomplete(AutocompleteResult result) +{ + RefPtr<Event> event; + switch (result) { + case AutocompleteResult::Success: + event = Event::create(eventNames().autocompleteEvent, false, false); + break; + case AutocompleteResult::ErrorDisabled: + event = AutocompleteErrorEvent::create("disabled"); + break; + case AutocompleteResult::ErrorCancel: + event = AutocompleteErrorEvent::create("cancel"); + break; + case AutocompleteResult::ErrorInvalid: + event = AutocompleteErrorEvent::create("invalid"); + break; + } + + event->setTarget(this); + m_pendingAutocompleteEvents.append(WTFMove(event)); + + // Dispatch events later as this API is meant to work asynchronously in all situations and implementations. + if (!m_requestAutocompleteTimer.isActive()) + m_requestAutocompleteTimer.startOneShot(0); } -void HTMLFormElement::setAutocapitalize(const AtomicString& value) +void HTMLFormElement::requestAutocompleteTimerFired() { - setAttribute(autocapitalizeAttr, value); + Vector<RefPtr<Event>> pendingEvents; + m_pendingAutocompleteEvents.swap(pendingEvents); + for (auto& pendingEvent : pendingEvents) + dispatchEvent(pendingEvent.release()); } + #endif void HTMLFormElement::parseAttribute(const QualifiedName& name, const AtomicString& value) { - if (name == actionAttr) + if (name == actionAttr) { m_attributes.parseAction(value); - else if (name == targetAttr) + + if (!m_attributes.action().isEmpty()) { + if (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); @@ -433,24 +499,13 @@ void HTMLFormElement::parseAttribute(const QualifiedName& name, const AtomicStri m_attributes.setAcceptCharset(value); else if (name == autocompleteAttr) { if (!shouldAutocomplete()) - document().registerForPageCacheSuspensionCallbacks(this); + document().registerForDocumentSuspensionCallbacks(this); else - document().unregisterForPageCacheSuspensionCallbacks(this); - } - else + document().unregisterForDocumentSuspensionCallbacks(this); + } else HTMLElement::parseAttribute(name, value); } -template<class T, size_t n> static void removeFromVector(Vector<T*, n> & vec, T* item) -{ - size_t size = vec.size(); - for (size_t i = 0; i != size; ++i) - if (vec[i] == item) { - vec.remove(i); - break; - } -} - unsigned HTMLFormElement::formElementIndexWithFormAttribute(Element* element, unsigned rangeStart, unsigned rangeEnd) { if (m_associatedElements.isEmpty()) @@ -470,7 +525,7 @@ unsigned HTMLFormElement::formElementIndexWithFormAttribute(Element* element, un while (left != right) { unsigned middle = left + ((right - left) / 2); ASSERT(middle < m_associatedElementsBeforeIndex || middle >= m_associatedElementsAfterIndex); - position = element->compareDocumentPosition(&m_associatedElements[middle]->asHTMLElement()); + position = element->compareDocumentPosition(m_associatedElements[middle]->asHTMLElement()); if (position & DOCUMENT_POSITION_FOLLOWING) right = middle; else @@ -478,7 +533,7 @@ unsigned HTMLFormElement::formElementIndexWithFormAttribute(Element* element, un } ASSERT(left < m_associatedElementsBeforeIndex || left >= m_associatedElementsAfterIndex); - position = element->compareDocumentPosition(&m_associatedElements[left]->asHTMLElement()); + position = element->compareDocumentPosition(m_associatedElements[left]->asHTMLElement()); if (position & DOCUMENT_POSITION_FOLLOWING) return left; return left + 1; @@ -492,8 +547,9 @@ unsigned HTMLFormElement::formElementIndex(FormAssociatedElement* associatedElem // Treats separately the case where this element has the form attribute // for performance consideration. - if (associatedHTMLElement.fastHasAttribute(formAttr)) { - unsigned short position = compareDocumentPosition(&associatedHTMLElement); + 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; @@ -506,7 +562,7 @@ unsigned HTMLFormElement::formElementIndex(FormAssociatedElement* associatedElem unsigned currentAssociatedElementsAfterIndex = m_associatedElementsAfterIndex; ++m_associatedElementsAfterIndex; - if (!associatedHTMLElement.isDescendantOf(this)) + if (!associatedHTMLElement.isDescendantOf(*this)) return currentAssociatedElementsAfterIndex; // Check for the special case where this element is the very last thing in @@ -523,7 +579,7 @@ unsigned HTMLFormElement::formElementIndex(FormAssociatedElement* associatedElem for (auto& element : descendants) { if (&element == &associatedHTMLElement) return i; - if (!isHTMLFormControlElement(element) && !isHTMLObjectElement(element)) + if (!is<HTMLFormControlElement>(element) && !is<HTMLObjectElement>(element)) continue; if (element.form() != this) continue; @@ -535,22 +591,49 @@ unsigned HTMLFormElement::formElementIndex(FormAssociatedElement* associatedElem 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; - for (index = 0; index < m_associatedElements.size(); ++index) { - if (m_associatedElements[index] == e) - break; - } + 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); - removeFromVector(m_associatedElements, 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.isEmpty()) + invalidateStyleForSubtree(); + m_invalidAssociatedFormControls.add(&formControlElement); +} + +void HTMLFormElement::removeInvalidAssociatedFormControlIfNeeded(const HTMLFormControlElement& formControlElement) +{ + if (m_invalidAssociatedFormControls.remove(&formControlElement)) { + if (m_invalidAssociatedFormControls.isEmpty()) + invalidateStyleForSubtree(); + } } bool HTMLFormElement::isURLAttribute(const Attribute& attribute) const @@ -566,14 +649,19 @@ void HTMLFormElement::registerImgElement(HTMLImageElement* e) void HTMLFormElement::removeImgElement(HTMLImageElement* e) { - ASSERT(m_imageElements.find(e) != notFound); removeFromPastNamesMap(e); - removeFromVector(m_imageElements, e); + bool removed = m_imageElements.removeFirst(e); + ASSERT_UNUSED(removed, removed); +} + +Ref<HTMLFormControlsCollection> HTMLFormElement::elements() +{ + return ensureRareData().ensureNodeLists().addCachedCollection<HTMLFormControlsCollection>(*this, FormControls); } -PassRefPtr<HTMLCollection> HTMLFormElement::elements() +Ref<HTMLCollection> HTMLFormElement::elementsForNativeBindings() { - return ensureCachedHTMLCollection(FormControls); + return elements(); } String HTMLFormElement::name() const @@ -583,7 +671,7 @@ String HTMLFormElement::name() const bool HTMLFormElement::noValidate() const { - return fastHasAttribute(novalidateAttr); + return hasAttributeWithoutSynchronization(novalidateAttr); } // FIXME: This function should be removed because it does not do the same thing as the @@ -591,17 +679,17 @@ bool HTMLFormElement::noValidate() const // (Darin Adler) removed this, someone added it back, so I am leaving it in for now. String HTMLFormElement::action() const { - return getAttribute(actionAttr); + return attributeWithoutSynchronization(actionAttr); } void HTMLFormElement::setAction(const String &value) { - setAttribute(actionAttr, value); + setAttributeWithoutSynchronization(actionAttr, value); } void HTMLFormElement::setEnctype(const String &value) { - setAttribute(enctypeAttr, value); + setAttributeWithoutSynchronization(enctypeAttr, value); } String HTMLFormElement::method() const @@ -611,12 +699,12 @@ String HTMLFormElement::method() const void HTMLFormElement::setMethod(const String &value) { - setAttribute(methodAttr, value); + setAttributeWithoutSynchronization(methodAttr, value); } String HTMLFormElement::target() const { - return getAttribute(targetAttr); + return attributeWithoutSynchronization(targetAttr); } bool HTMLFormElement::wasUserSubmitted() const @@ -626,43 +714,71 @@ bool HTMLFormElement::wasUserSubmitted() const HTMLFormControlElement* HTMLFormElement::defaultButton() const { - for (unsigned i = 0; i < m_associatedElements.size(); ++i) { - if (!m_associatedElements[i]->isFormControlElement()) - continue; - HTMLFormControlElement* control = toHTMLFormControlElement(m_associatedElements[i]); - if (control->isSuccessfulSubmitButton()) - return control; + if (!m_defaultButton) { + for (auto& associatedElement : m_associatedElements) { + if (!is<HTMLFormControlElement>(*associatedElement)) + continue; + HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*associatedElement); + if (control.isSuccessfulSubmitButton()) { + m_defaultButton = &control; + break; + } + } } + return m_defaultButton; +} - 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; + } + + HTMLFormControlElement* oldDefault = m_defaultButton; + m_defaultButton = nullptr; + defaultButton(); + if (m_defaultButton != oldDefault) { + if (oldDefault) + oldDefault->invalidateStyleForSubtree(); + if (m_defaultButton) + m_defaultButton->invalidateStyleForSubtree(); + } } bool HTMLFormElement::checkValidity() { - Vector<RefPtr<FormAssociatedElement>> controls; + Vector<RefPtr<HTMLFormControlElement>> controls; return !checkInvalidControlsAndCollectUnhandled(controls); } -bool HTMLFormElement::checkInvalidControlsAndCollectUnhandled(Vector<RefPtr<FormAssociatedElement>>& unhandledInvalidControls) +bool HTMLFormElement::checkInvalidControlsAndCollectUnhandled(Vector<RefPtr<HTMLFormControlElement>>& unhandledInvalidControls) { - Ref<HTMLFormElement> protect(*this); + 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 (unsigned i = 0; i < m_associatedElements.size(); ++i) - elements.append(m_associatedElements[i]); + for (auto& associatedElement : m_associatedElements) + elements.append(associatedElement); bool hasInvalidControls = false; - for (unsigned i = 0; i < elements.size(); ++i) { - if (elements[i]->form() == this && elements[i]->isFormControlElement()) { - HTMLFormControlElement* control = toHTMLFormControlElement(elements[i].get()); - if (!control->checkValidity(&unhandledInvalidControls) && control->form() == this) + 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() +{ + return validateInteractively(); +} + #ifndef NDEBUG void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem* item) const { @@ -676,7 +792,7 @@ void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem* item) const } ASSERT_WITH_SECURITY_IMPLICATION(element.hasTagName(imgTag)); - ASSERT_WITH_SECURITY_IMPLICATION(m_imageElements.find(&toHTMLImageElement(element)) != notFound); + ASSERT_WITH_SECURITY_IMPLICATION(m_imageElements.find(&downcast<HTMLImageElement>(element)) != notFound); } #else inline void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem*) const @@ -701,7 +817,7 @@ void HTMLFormElement::addToPastNamesMap(FormNamedItem* item, const AtomicString& if (pastName.isEmpty()) return; if (!m_pastNamesMap) - m_pastNamesMap = adoptPtr(new PastNamesMap); + m_pastNamesMap = std::make_unique<PastNamesMap>(); m_pastNamesMap->set(pastName.impl(), item); } @@ -711,47 +827,51 @@ void HTMLFormElement::removeFromPastNamesMap(FormNamedItem* item) if (!m_pastNamesMap) return; - PastNamesMap::iterator end = m_pastNamesMap->end(); - for (PastNamesMap::iterator it = m_pastNamesMap->begin(); it != end; ++it) { - if (it->value == item) - it->value = 0; // Keep looping. Single element can have multiple names. + for (auto& pastName : m_pastNamesMap->values()) { + if (pastName == item) + pastName = nullptr; // Keep looping. Single element can have multiple names. } } -bool HTMLFormElement::hasNamedElement(const AtomicString& name) +bool HTMLFormElement::matchesValidPseudoClass() const { - return elements()->hasNamedItem(name) || elementFromPastNamesMap(name); + return m_invalidAssociatedFormControls.isEmpty(); } -// FIXME: Use RefPtr<HTMLElement> for namedItems. elements()->namedItems never return non-HTMLElement nodes. -void HTMLFormElement::getNamedElements(const AtomicString& name, Vector<Ref<Element>>& namedItems) +bool HTMLFormElement::matchesInvalidPseudoClass() const +{ + return !m_invalidAssociatedFormControls.isEmpty(); +} + +// FIXME: Use Ref<HTMLElement> for the function result since there are no non-HTML elements returned here. +Vector<Ref<Element>> HTMLFormElement::namedElements(const AtomicString& name) { // http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem - elements()->namedItems(name, namedItems); + Vector<Ref<Element>> namedItems = elements()->namedItems(name); HTMLElement* elementFromPast = elementFromPastNamesMap(name); - if (namedItems.size() == 1 && &namedItems.first().get() != elementFromPast) - addToPastNamesMap(toHTMLElement(&namedItems.first().get())->asFormNamedItem(), 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::documentDidResumeFromPageCache() +void HTMLFormElement::resumeFromDocumentSuspension() { ASSERT(!shouldAutocomplete()); - for (unsigned i = 0; i < m_associatedElements.size(); ++i) { - if (m_associatedElements[i]->isFormControlElement()) - toHTMLFormControlElement(m_associatedElements[i])->reset(); - } + Ref<HTMLFormElement> protectedThis(*this); + + resetAssociatedFormControlElements(); } -void HTMLFormElement::didMoveToNewDocument(Document* oldDocument) +void HTMLFormElement::didMoveToNewDocument(Document& oldDocument) { if (!shouldAutocomplete()) { - if (oldDocument) - oldDocument->unregisterForPageCacheSuspensionCallbacks(this); - document().registerForPageCacheSuspensionCallbacks(this); + oldDocument.unregisterForDocumentSuspensionCallbacks(this); + document().registerForDocumentSuspensionCallbacks(this); } HTMLElement::didMoveToNewDocument(oldDocument); @@ -759,7 +879,7 @@ void HTMLFormElement::didMoveToNewDocument(Document* oldDocument) bool HTMLFormElement::shouldAutocomplete() const { - return !equalIgnoringCase(fastGetAttribute(autocompleteAttr), "off"); + return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(autocompleteAttr), "off"); } void HTMLFormElement::finishParsingChildren() @@ -779,4 +899,17 @@ HTMLFormElement* HTMLFormElement::findClosestFormAncestor(const Element& startEl return const_cast<HTMLFormElement*>(ancestorsOfType<HTMLFormElement>(startElement).first()); } +void HTMLFormElement::setAutocomplete(const AtomicString& value) +{ + setAttributeWithoutSynchronization(autocompleteAttr, value); +} + +const AtomicString& HTMLFormElement::autocomplete() const +{ + static NeverDestroyed<AtomicString> on("on", AtomicString::ConstructFromLiteral); + static NeverDestroyed<AtomicString> off("off", AtomicString::ConstructFromLiteral); + + return equalIgnoringASCIICase(attributeWithoutSynchronization(autocompleteAttr), "off") ? off : on; +} + } // namespace |