2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
6 * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
26 #include "core/html/HTMLFormElement.h"
28 #include "bindings/core/v8/ScriptController.h"
29 #include "bindings/core/v8/ScriptEventListener.h"
30 #include "bindings/core/v8/UnionTypesCore.h"
31 #include "bindings/core/v8/V8DOMActivityLogger.h"
32 #include "core/HTMLNames.h"
33 #include "core/dom/Attribute.h"
34 #include "core/dom/Document.h"
35 #include "core/dom/ElementTraversal.h"
36 #include "core/dom/IdTargetObserverRegistry.h"
37 #include "core/dom/NodeListsNodeData.h"
38 #include "core/events/AutocompleteErrorEvent.h"
39 #include "core/events/Event.h"
40 #include "core/events/GenericEventQueue.h"
41 #include "core/events/ScopedEventQueue.h"
42 #include "core/frame/LocalDOMWindow.h"
43 #include "core/frame/LocalFrame.h"
44 #include "core/frame/UseCounter.h"
45 #include "core/frame/csp/ContentSecurityPolicy.h"
46 #include "core/html/HTMLCollection.h"
47 #include "core/html/HTMLDialogElement.h"
48 #include "core/html/HTMLFormControlsCollection.h"
49 #include "core/html/HTMLImageElement.h"
50 #include "core/html/HTMLInputElement.h"
51 #include "core/html/HTMLObjectElement.h"
52 #include "core/html/RadioNodeList.h"
53 #include "core/html/forms/FormController.h"
54 #include "core/inspector/ConsoleMessage.h"
55 #include "core/layout/LayoutTextControl.h"
56 #include "core/loader/FrameLoader.h"
57 #include "core/loader/FrameLoaderClient.h"
58 #include "core/loader/MixedContentChecker.h"
59 #include "platform/UserGestureIndicator.h"
60 #include "wtf/text/AtomicString.h"
65 using namespace HTMLNames
;
67 HTMLFormElement::HTMLFormElement(Document
& document
)
68 : HTMLElement(formTag
, document
)
70 , m_weakPtrFactory(this)
72 , m_associatedElementsAreDirty(false)
73 , m_imageElementsAreDirty(false)
74 , m_hasElementsAssociatedByParser(false)
75 , m_didFinishParsingChildren(false)
76 , m_wasUserSubmitted(false)
77 , m_isSubmittingOrInUserJSSubmitEvent(false)
78 , m_shouldSubmit(false)
79 , m_isInResetFunction(false)
81 , m_pendingAutocompleteEventsQueue(GenericEventQueue::create(this))
85 PassRefPtrWillBeRawPtr
<HTMLFormElement
> HTMLFormElement::create(Document
& document
)
87 UseCounter::count(document
, UseCounter::FormElement
);
88 return adoptRefWillBeNoop(new HTMLFormElement(document
));
91 HTMLFormElement::~HTMLFormElement()
94 // With Oilpan, either removedFrom is called or the document and
95 // form controller are dead as well and there is no need to remove
96 // this form element from it.
97 document().formController().willDeleteForm(this);
101 DEFINE_TRACE(HTMLFormElement
)
104 visitor
->trace(m_pastNamesMap
);
105 visitor
->trace(m_radioButtonGroupScope
);
106 visitor
->trace(m_associatedElements
);
107 visitor
->trace(m_imageElements
);
108 visitor
->trace(m_pendingAutocompleteEventsQueue
);
110 HTMLElement::trace(visitor
);
113 bool HTMLFormElement::matchesValidityPseudoClasses() const
118 bool HTMLFormElement::isValidElement()
120 return !checkInvalidControlsAndCollectUnhandled(0, CheckValidityDispatchNoEvent
);
123 bool HTMLFormElement::layoutObjectIsNeeded(const ComputedStyle
& style
)
126 return HTMLElement::layoutObjectIsNeeded(style
);
128 ContainerNode
* node
= parentNode();
129 if (!node
|| !node
->layoutObject())
130 return HTMLElement::layoutObjectIsNeeded(style
);
131 LayoutObject
* parentLayoutObject
= node
->layoutObject();
132 // FIXME: Shouldn't we also check for table caption (see |formIsTablePart| below).
133 // FIXME: This check is not correct for Shadow DOM.
134 bool parentIsTableElementPart
= (parentLayoutObject
->isTable() && isHTMLTableElement(*node
))
135 || (parentLayoutObject
->isTableRow() && isHTMLTableRowElement(*node
))
136 || (parentLayoutObject
->isTableSection() && node
->hasTagName(tbodyTag
))
137 || (parentLayoutObject
->isLayoutTableCol() && node
->hasTagName(colTag
))
138 || (parentLayoutObject
->isTableCell() && isHTMLTableRowElement(*node
));
140 if (!parentIsTableElementPart
)
143 EDisplay display
= style
.display();
144 bool formIsTablePart
= display
== TABLE
|| display
== INLINE_TABLE
|| display
== TABLE_ROW_GROUP
145 || display
== TABLE_HEADER_GROUP
|| display
== TABLE_FOOTER_GROUP
|| display
== TABLE_ROW
146 || display
== TABLE_COLUMN_GROUP
|| display
== TABLE_COLUMN
|| display
== TABLE_CELL
147 || display
== TABLE_CAPTION
;
149 return formIsTablePart
;
152 Node::InsertionNotificationRequest
HTMLFormElement::insertedInto(ContainerNode
* insertionPoint
)
154 if (insertionPoint
->inDocument()) {
155 V8DOMActivityLogger
* activityLogger
= V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
156 if (activityLogger
) {
159 argv
.append(fastGetAttribute(methodAttr
));
160 argv
.append(fastGetAttribute(actionAttr
));
161 activityLogger
->logEvent("blinkAddElement", argv
.size(), argv
.data());
164 HTMLElement::insertedInto(insertionPoint
);
165 if (insertionPoint
->inDocument())
166 this->document().didAssociateFormControl(this);
167 return InsertionDone
;
171 void notifyFormRemovedFromTree(const T
& elements
, Node
& root
)
173 for (const auto& element
: elements
)
174 element
->formRemovedFromTree(root
);
177 void HTMLFormElement::removedFrom(ContainerNode
* insertionPoint
)
179 // We don't need to take care of form association by 'form' content
180 // attribute becuse IdTargetObserver handles it.
181 if (m_hasElementsAssociatedByParser
) {
182 Node
& root
= NodeTraversal::highestAncestorOrSelf(*this);
183 if (!m_associatedElementsAreDirty
) {
184 FormAssociatedElement::List
elements(associatedElements());
185 notifyFormRemovedFromTree(elements
, root
);
187 FormAssociatedElement::List elements
;
188 collectAssociatedElements(NodeTraversal::highestAncestorOrSelf(*insertionPoint
), elements
);
189 notifyFormRemovedFromTree(elements
, root
);
190 collectAssociatedElements(root
, elements
);
191 notifyFormRemovedFromTree(elements
, root
);
194 if (!m_imageElementsAreDirty
) {
195 WillBeHeapVector
<RawPtrWillBeMember
<HTMLImageElement
>> images(imageElements());
196 notifyFormRemovedFromTree(images
, root
);
198 WillBeHeapVector
<RawPtrWillBeMember
<HTMLImageElement
>> images
;
199 collectImageElements(NodeTraversal::highestAncestorOrSelf(*insertionPoint
), images
);
200 notifyFormRemovedFromTree(images
, root
);
201 collectImageElements(root
, images
);
202 notifyFormRemovedFromTree(images
, root
);
206 document().formController().willDeleteForm(this);
208 HTMLElement::removedFrom(insertionPoint
);
211 void HTMLFormElement::handleLocalEvents(Event
& event
)
213 Node
* targetNode
= event
.target()->toNode();
214 if (event
.eventPhase() != Event::CAPTURING_PHASE
&& targetNode
&& targetNode
!= this && (event
.type() == EventTypeNames::submit
|| event
.type() == EventTypeNames::reset
)) {
215 event
.stopPropagation();
218 HTMLElement::handleLocalEvents(event
);
221 unsigned HTMLFormElement::length() const
223 const FormAssociatedElement::List
& elements
= associatedElements();
225 for (unsigned i
= 0; i
< elements
.size(); ++i
) {
226 if (elements
[i
]->isEnumeratable())
232 HTMLElement
* HTMLFormElement::item(unsigned index
)
234 return elements()->item(index
);
237 void HTMLFormElement::submitImplicitly(Event
* event
, bool fromImplicitSubmissionTrigger
)
239 int submissionTriggerCount
= 0;
240 bool seenDefaultButton
= false;
241 const FormAssociatedElement::List
& elements
= associatedElements();
242 for (unsigned i
= 0; i
< elements
.size(); ++i
) {
243 FormAssociatedElement
* formAssociatedElement
= elements
[i
];
244 if (!formAssociatedElement
->isFormControlElement())
246 HTMLFormControlElement
* control
= toHTMLFormControlElement(formAssociatedElement
);
247 if (!seenDefaultButton
&& control
->canBeSuccessfulSubmitButton()) {
248 if (fromImplicitSubmissionTrigger
)
249 seenDefaultButton
= true;
250 if (control
->isSuccessfulSubmitButton()) {
251 control
->dispatchSimulatedClick(event
);
254 if (fromImplicitSubmissionTrigger
) {
255 // Default (submit) button is not activated; no implicit submission.
258 } else if (control
->canTriggerImplicitSubmission()) {
259 ++submissionTriggerCount
;
262 if (fromImplicitSubmissionTrigger
&& submissionTriggerCount
== 1)
263 prepareForSubmission(event
);
266 // FIXME: Consolidate this and similar code in FormSubmission.cpp.
267 static inline HTMLFormControlElement
* submitElementFromEvent(const Event
* event
)
269 for (Node
* node
= event
->target()->toNode(); node
; node
= node
->parentOrShadowHostNode()) {
270 if (node
->isElementNode() && toElement(node
)->isFormControlElement())
271 return toHTMLFormControlElement(node
);
276 bool HTMLFormElement::validateInteractively()
278 const FormAssociatedElement::List
& elements
= associatedElements();
279 for (unsigned i
= 0; i
< elements
.size(); ++i
) {
280 if (elements
[i
]->isFormControlElement())
281 toHTMLFormControlElement(elements
[i
])->hideVisibleValidationMessage();
284 WillBeHeapVector
<RefPtrWillBeMember
<HTMLFormControlElement
>> unhandledInvalidControls
;
285 if (!checkInvalidControlsAndCollectUnhandled(&unhandledInvalidControls
, CheckValidityDispatchInvalidEvent
))
287 // Because the form has invalid controls, we abort the form submission and
288 // show a validation message on a focusable form control.
290 // Needs to update layout now because we'd like to call isFocusable(), which
291 // has !layoutObject()->needsLayout() assertion.
292 document().updateLayoutIgnorePendingStylesheets();
294 RefPtrWillBeRawPtr
<HTMLFormElement
> protector(this);
295 // Focus on the first focusable control and show a validation message.
296 for (unsigned i
= 0; i
< unhandledInvalidControls
.size(); ++i
) {
297 HTMLFormControlElement
* unhandled
= unhandledInvalidControls
[i
].get();
298 if (unhandled
->isFocusable()) {
299 unhandled
->showValidationMessage();
303 // Warn about all of unfocusable controls.
304 if (document().frame()) {
305 for (unsigned i
= 0; i
< unhandledInvalidControls
.size(); ++i
) {
306 HTMLFormControlElement
* unhandled
= unhandledInvalidControls
[i
].get();
307 if (unhandled
->isFocusable())
309 String
message("An invalid form control with name='%name' is not focusable.");
310 message
.replace("%name", unhandled
->name());
311 document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSource
, ErrorMessageLevel
, message
));
317 void HTMLFormElement::prepareForSubmission(Event
* event
)
319 RefPtrWillBeRawPtr
<HTMLFormElement
> protector(this);
320 LocalFrame
* frame
= document().frame();
321 if (!frame
|| m_isSubmittingOrInUserJSSubmitEvent
)
324 bool skipValidation
= !document().page() || noValidate();
326 HTMLFormControlElement
* submitElement
= submitElementFromEvent(event
);
327 if (submitElement
&& submitElement
->formNoValidate())
328 skipValidation
= true;
330 // Interactive validation must be done before dispatching the submit event.
331 if (!skipValidation
&& !validateInteractively())
334 m_isSubmittingOrInUserJSSubmitEvent
= true;
335 m_shouldSubmit
= false;
337 frame
->loader().client()->dispatchWillSendSubmitEvent(this);
339 if (dispatchEvent(Event::createCancelableBubble(EventTypeNames::submit
)))
340 m_shouldSubmit
= true;
342 m_isSubmittingOrInUserJSSubmitEvent
= false;
345 submit(event
, true, true);
348 void HTMLFormElement::submitFromJavaScript()
350 submit(0, false, UserGestureIndicator::processingUserGesture());
353 void HTMLFormElement::submitDialog(PassRefPtrWillBeRawPtr
<FormSubmission
> formSubmission
)
355 for (Node
* node
= this; node
; node
= node
->parentOrShadowHostNode()) {
356 if (isHTMLDialogElement(*node
)) {
357 toHTMLDialogElement(*node
).closeDialog(formSubmission
->result());
363 void HTMLFormElement::submit(Event
* event
, bool activateSubmitButton
, bool processingUserGesture
)
365 FrameView
* view
= document().view();
366 LocalFrame
* frame
= document().frame();
367 if (!view
|| !frame
|| !frame
->page())
370 if (m_isSubmittingOrInUserJSSubmitEvent
) {
371 m_shouldSubmit
= true;
375 m_isSubmittingOrInUserJSSubmitEvent
= true;
376 m_wasUserSubmitted
= processingUserGesture
;
378 RefPtrWillBeRawPtr
<HTMLFormControlElement
> firstSuccessfulSubmitButton
= nullptr;
379 bool needButtonActivation
= activateSubmitButton
; // do we need to activate a submit button?
381 const FormAssociatedElement::List
& elements
= associatedElements();
382 for (unsigned i
= 0; i
< elements
.size(); ++i
) {
383 FormAssociatedElement
* associatedElement
= elements
[i
];
384 if (!associatedElement
->isFormControlElement())
386 if (needButtonActivation
) {
387 HTMLFormControlElement
* control
= toHTMLFormControlElement(associatedElement
);
388 if (control
->isActivatedSubmit())
389 needButtonActivation
= false;
390 else if (firstSuccessfulSubmitButton
== 0 && control
->isSuccessfulSubmitButton())
391 firstSuccessfulSubmitButton
= control
;
395 if (needButtonActivation
&& firstSuccessfulSubmitButton
)
396 firstSuccessfulSubmitButton
->setActivatedSubmit(true);
398 RefPtrWillBeRawPtr
<FormSubmission
> formSubmission
= FormSubmission::create(this, m_attributes
, event
);
399 EventQueueScope scopeForDialogClose
; // Delay dispatching 'close' to dialog until done submitting.
400 if (formSubmission
->method() == FormSubmission::DialogMethod
)
401 submitDialog(formSubmission
.release());
403 scheduleFormSubmission(formSubmission
.release());
405 if (needButtonActivation
&& firstSuccessfulSubmitButton
)
406 firstSuccessfulSubmitButton
->setActivatedSubmit(false);
408 m_shouldSubmit
= false;
409 m_isSubmittingOrInUserJSSubmitEvent
= false;
412 void HTMLFormElement::scheduleFormSubmission(PassRefPtrWillBeRawPtr
<FormSubmission
> submission
)
414 ASSERT(submission
->method() == FormSubmission::PostMethod
|| submission
->method() == FormSubmission::GetMethod
);
415 ASSERT(submission
->data());
416 ASSERT(submission
->form());
417 if (submission
->action().isEmpty())
419 if (document().isSandboxed(SandboxForms
)) {
420 // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
421 document().addConsoleMessage(ConsoleMessage::create(SecurityMessageSource
, ErrorMessageLevel
, "Blocked form submission to '" + submission
->action().elidedString() + "' because the form's frame is sandboxed and the 'allow-forms' permission is not set."));
425 if (protocolIsJavaScript(submission
->action())) {
426 if (!document().contentSecurityPolicy()->allowFormAction(submission
->action()))
428 document().frame()->script().executeScriptIfJavaScriptURL(submission
->action());
432 Frame
* targetFrame
= document().frame()->findFrameForNavigation(submission
->target(), *document().frame());
434 if (!LocalDOMWindow::allowPopUp(*document().frame()) && !UserGestureIndicator::processingUserGesture())
436 targetFrame
= document().frame();
438 submission
->clearTarget();
440 if (!targetFrame
->host())
443 UseCounter::count(document(), UseCounter::FormsSubmitted
);
444 if (MixedContentChecker::isMixedFormAction(document().frame(), submission
->action()))
445 UseCounter::count(document().frame(), UseCounter::MixedContentFormsSubmitted
);
447 // FIXME: Plumb form submission for remote frames.
448 if (targetFrame
->isLocalFrame())
449 toLocalFrame(targetFrame
)->navigationScheduler().scheduleFormSubmission(&document(), submission
);
452 void HTMLFormElement::reset()
454 LocalFrame
* frame
= document().frame();
455 if (m_isInResetFunction
|| !frame
)
458 m_isInResetFunction
= true;
460 if (!dispatchEvent(Event::createCancelableBubble(EventTypeNames::reset
))) {
461 m_isInResetFunction
= false;
465 const FormAssociatedElement::List
& elements
= associatedElements();
466 for (unsigned i
= 0; i
< elements
.size(); ++i
) {
467 if (elements
[i
]->isFormControlElement())
468 toHTMLFormControlElement(elements
[i
])->reset();
471 m_isInResetFunction
= false;
474 void HTMLFormElement::requestAutocomplete()
478 if (!document().frame())
479 errorMessage
= "requestAutocomplete: form is not owned by a displayed document.";
480 else if (!shouldAutocomplete())
481 errorMessage
= "requestAutocomplete: form autocomplete attribute is set to off.";
482 else if (!UserGestureIndicator::processingUserGesture())
483 errorMessage
= "requestAutocomplete: must be called in response to a user gesture.";
485 if (!errorMessage
.isEmpty()) {
486 document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSource
, LogMessageLevel
, errorMessage
));
487 finishRequestAutocomplete(AutocompleteResultErrorDisabled
);
489 document().frame()->loader().client()->didRequestAutocomplete(this);
493 void HTMLFormElement::finishRequestAutocomplete(AutocompleteResult result
)
495 RefPtrWillBeRawPtr
<Event
> event
= nullptr;
496 if (result
== AutocompleteResultSuccess
)
497 event
= Event::createBubble(EventTypeNames::autocomplete
);
498 else if (result
== AutocompleteResultErrorDisabled
)
499 event
= AutocompleteErrorEvent::create("disabled");
500 else if (result
== AutocompleteResultErrorCancel
)
501 event
= AutocompleteErrorEvent::create("cancel");
502 else if (result
== AutocompleteResultErrorInvalid
)
503 event
= AutocompleteErrorEvent::create("invalid");
505 ASSERT_NOT_REACHED();
507 event
->setTarget(this);
508 m_pendingAutocompleteEventsQueue
->enqueueEvent(event
.release());
511 void HTMLFormElement::parseAttribute(const QualifiedName
& name
, const AtomicString
& value
)
513 if (name
== actionAttr
) {
514 m_attributes
.parseAction(value
);
515 // If the new action attribute is pointing to insecure "action" location from a secure page
516 // it is marked as "passive" mixed content.
517 KURL actionURL
= document().completeURL(m_attributes
.action().isEmpty() ? document().url().string() : m_attributes
.action());
518 if (MixedContentChecker::isMixedFormAction(document().frame(), actionURL
))
519 UseCounter::count(document().frame(), UseCounter::MixedContentFormPresent
);
520 } else if (name
== targetAttr
) {
521 m_attributes
.setTarget(value
);
522 } else if (name
== methodAttr
) {
523 m_attributes
.updateMethodType(value
);
524 } else if (name
== enctypeAttr
) {
525 m_attributes
.updateEncodingType(value
);
526 } else if (name
== accept_charsetAttr
) {
527 m_attributes
.setAcceptCharset(value
);
528 } else if (name
== onautocompleteAttr
) {
529 setAttributeEventListener(EventTypeNames::autocomplete
, createAttributeEventListener(this, name
, value
, eventParameterName()));
530 } else if (name
== onautocompleteerrorAttr
) {
531 setAttributeEventListener(EventTypeNames::autocompleteerror
, createAttributeEventListener(this, name
, value
, eventParameterName()));
533 HTMLElement::parseAttribute(name
, value
);
537 void HTMLFormElement::attributeWillChange(const QualifiedName
& name
, const AtomicString
& oldValue
, const AtomicString
& newValue
)
539 if (name
== actionAttr
&& inDocument()) {
540 V8DOMActivityLogger
* activityLogger
= V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
541 if (activityLogger
) {
544 argv
.append(actionAttr
.toString());
545 argv
.append(oldValue
);
546 argv
.append(newValue
);
547 activityLogger
->logEvent("blinkSetAttribute", argv
.size(), argv
.data());
550 HTMLElement::attributeWillChange(name
, oldValue
, newValue
);
553 void HTMLFormElement::associate(FormAssociatedElement
& e
)
555 m_associatedElementsAreDirty
= true;
556 m_associatedElements
.clear();
559 void HTMLFormElement::disassociate(FormAssociatedElement
& e
)
561 m_associatedElementsAreDirty
= true;
562 m_associatedElements
.clear();
563 removeFromPastNamesMap(toHTMLElement(e
));
566 bool HTMLFormElement::isURLAttribute(const Attribute
& attribute
) const
568 return attribute
.name() == actionAttr
|| HTMLElement::isURLAttribute(attribute
);
571 bool HTMLFormElement::hasLegalLinkAttribute(const QualifiedName
& name
) const
573 return name
== actionAttr
|| HTMLElement::hasLegalLinkAttribute(name
);
576 void HTMLFormElement::associate(HTMLImageElement
& e
)
578 m_imageElementsAreDirty
= true;
579 m_imageElements
.clear();
582 void HTMLFormElement::disassociate(HTMLImageElement
& e
)
584 m_imageElementsAreDirty
= true;
585 m_imageElements
.clear();
586 removeFromPastNamesMap(e
);
590 WeakPtr
<HTMLFormElement
> HTMLFormElement::createWeakPtr()
592 return m_weakPtrFactory
.createWeakPtr();
596 void HTMLFormElement::didAssociateByParser()
598 if (!m_didFinishParsingChildren
)
600 m_hasElementsAssociatedByParser
= true;
601 UseCounter::count(document(), UseCounter::FormAssociationByParser
);
604 PassRefPtrWillBeRawPtr
<HTMLFormControlsCollection
> HTMLFormElement::elements()
606 return ensureCachedCollection
<HTMLFormControlsCollection
>(FormControls
);
609 void HTMLFormElement::collectAssociatedElements(Node
& root
, FormAssociatedElement::List
& elements
) const
612 for (HTMLElement
& element
: Traversal
<HTMLElement
>::startsAfter(root
)) {
613 FormAssociatedElement
* associatedElement
= 0;
614 if (element
.isFormControlElement())
615 associatedElement
= toHTMLFormControlElement(&element
);
616 else if (isHTMLObjectElement(element
))
617 associatedElement
= toHTMLObjectElement(&element
);
620 if (associatedElement
->form()== this)
621 elements
.append(associatedElement
);
625 // This function should be const conceptually. However we update some fields
626 // because of lazy evaluation.
627 const FormAssociatedElement::List
& HTMLFormElement::associatedElements() const
629 if (!m_associatedElementsAreDirty
)
630 return m_associatedElements
;
631 HTMLFormElement
* mutableThis
= const_cast<HTMLFormElement
*>(this);
632 Node
* scope
= mutableThis
;
633 if (m_hasElementsAssociatedByParser
)
634 scope
= &NodeTraversal::highestAncestorOrSelf(*mutableThis
);
635 if (inDocument() && treeScope().idTargetObserverRegistry().hasObservers(fastGetAttribute(idAttr
)))
636 scope
= &treeScope().rootNode();
638 collectAssociatedElements(*scope
, mutableThis
->m_associatedElements
);
639 mutableThis
->m_associatedElementsAreDirty
= false;
640 return m_associatedElements
;
643 void HTMLFormElement::collectImageElements(Node
& root
, WillBeHeapVector
<RawPtrWillBeMember
<HTMLImageElement
>>& elements
)
646 for (HTMLImageElement
& image
: Traversal
<HTMLImageElement
>::startsAfter(root
)) {
647 if (image
.formOwner() == this)
648 elements
.append(&image
);
652 const WillBeHeapVector
<RawPtrWillBeMember
<HTMLImageElement
>>& HTMLFormElement::imageElements()
654 if (!m_imageElementsAreDirty
)
655 return m_imageElements
;
656 collectImageElements(m_hasElementsAssociatedByParser
? NodeTraversal::highestAncestorOrSelf(*this) : *this, m_imageElements
);
657 m_imageElementsAreDirty
= false;
658 return m_imageElements
;
661 String
HTMLFormElement::name() const
663 return getNameAttribute();
666 bool HTMLFormElement::noValidate() const
668 return fastHasAttribute(novalidateAttr
);
671 // FIXME: This function should be removed because it does not do the same thing as the
672 // JavaScript binding for action, which treats action as a URL attribute. Last time I
673 // (Darin Adler) removed this, someone added it back, so I am leaving it in for now.
674 const AtomicString
& HTMLFormElement::action() const
676 return getAttribute(actionAttr
);
679 void HTMLFormElement::setEnctype(const AtomicString
& value
)
681 setAttribute(enctypeAttr
, value
);
684 String
HTMLFormElement::method() const
686 return FormSubmission::Attributes::methodString(m_attributes
.method());
689 void HTMLFormElement::setMethod(const AtomicString
& value
)
691 setAttribute(methodAttr
, value
);
694 bool HTMLFormElement::wasUserSubmitted() const
696 return m_wasUserSubmitted
;
699 HTMLFormControlElement
* HTMLFormElement::defaultButton() const
701 const FormAssociatedElement::List
& elements
= associatedElements();
702 for (unsigned i
= 0; i
< elements
.size(); ++i
) {
703 if (!elements
[i
]->isFormControlElement())
705 HTMLFormControlElement
* control
= toHTMLFormControlElement(elements
[i
]);
706 if (control
->isSuccessfulSubmitButton())
713 bool HTMLFormElement::checkValidity()
715 return !checkInvalidControlsAndCollectUnhandled(0, CheckValidityDispatchInvalidEvent
);
718 bool HTMLFormElement::checkInvalidControlsAndCollectUnhandled(WillBeHeapVector
<RefPtrWillBeMember
<HTMLFormControlElement
>>* unhandledInvalidControls
, CheckValidityEventBehavior eventBehavior
)
720 RefPtrWillBeRawPtr
<HTMLFormElement
> protector(this);
721 // Copy associatedElements because event handlers called from
722 // HTMLFormControlElement::checkValidity() might change associatedElements.
723 const FormAssociatedElement::List
& associatedElements
= this->associatedElements();
724 WillBeHeapVector
<RefPtrWillBeMember
<FormAssociatedElement
>> elements
;
725 elements
.reserveCapacity(associatedElements
.size());
726 for (unsigned i
= 0; i
< associatedElements
.size(); ++i
)
727 elements
.append(associatedElements
[i
]);
728 int invalidControlsCount
= 0;
729 for (unsigned i
= 0; i
< elements
.size(); ++i
) {
730 if (elements
[i
]->form() == this && elements
[i
]->isFormControlElement()) {
731 HTMLFormControlElement
* control
= toHTMLFormControlElement(elements
[i
].get());
732 if (control
->isSubmittableElement() && !control
->checkValidity(unhandledInvalidControls
, eventBehavior
) && control
->formOwner() == this) {
733 ++invalidControlsCount
;
734 if (!unhandledInvalidControls
&& eventBehavior
== CheckValidityDispatchNoEvent
)
739 return invalidControlsCount
;
742 bool HTMLFormElement::reportValidity()
744 return validateInteractively();
747 Element
* HTMLFormElement::elementFromPastNamesMap(const AtomicString
& pastName
)
749 if (pastName
.isEmpty() || !m_pastNamesMap
)
751 Element
* element
= m_pastNamesMap
->get(pastName
);
755 ASSERT_WITH_SECURITY_IMPLICATION(toHTMLElement(element
)->formOwner() == this);
756 if (isHTMLImageElement(*element
)) {
757 ASSERT_WITH_SECURITY_IMPLICATION(imageElements().find(element
) != kNotFound
);
758 } else if (isHTMLObjectElement(*element
)) {
759 ASSERT_WITH_SECURITY_IMPLICATION(associatedElements().find(toHTMLObjectElement(element
)) != kNotFound
);
761 ASSERT_WITH_SECURITY_IMPLICATION(associatedElements().find(toHTMLFormControlElement(element
)) != kNotFound
);
767 void HTMLFormElement::addToPastNamesMap(Element
* element
, const AtomicString
& pastName
)
769 if (pastName
.isEmpty())
772 m_pastNamesMap
= adoptPtrWillBeNoop(new PastNamesMap
);
773 m_pastNamesMap
->set(pastName
, element
);
776 void HTMLFormElement::removeFromPastNamesMap(HTMLElement
& element
)
780 for (auto& it
: *m_pastNamesMap
) {
781 if (it
.value
== &element
) {
783 // Keep looping. Single element can have multiple names.
788 void HTMLFormElement::getNamedElements(const AtomicString
& name
, WillBeHeapVector
<RefPtrWillBeMember
<Element
>>& namedItems
)
790 // http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem
791 elements()->namedItems(name
, namedItems
);
793 Element
* elementFromPast
= elementFromPastNamesMap(name
);
794 if (namedItems
.size() && namedItems
.first() != elementFromPast
) {
795 addToPastNamesMap(namedItems
.first().get(), name
);
796 } else if (elementFromPast
&& namedItems
.isEmpty()) {
797 namedItems
.append(elementFromPast
);
798 UseCounter::count(document(), UseCounter::FormNameAccessForPastNamesMap
);
802 bool HTMLFormElement::shouldAutocomplete() const
804 return !equalIgnoringCase(fastGetAttribute(autocompleteAttr
), "off");
807 void HTMLFormElement::finishParsingChildren()
809 HTMLElement::finishParsingChildren();
810 document().formController().restoreControlStateIn(*this);
811 m_didFinishParsingChildren
= true;
814 void HTMLFormElement::copyNonAttributePropertiesFromElement(const Element
& source
)
816 m_wasDemoted
= static_cast<const HTMLFormElement
&>(source
).m_wasDemoted
;
817 HTMLElement::copyNonAttributePropertiesFromElement(source
);
820 void HTMLFormElement::anonymousNamedGetter(const AtomicString
& name
, RadioNodeListOrElement
& returnValue
)
822 // Call getNamedElements twice, first time check if it has a value
823 // and let HTMLFormElement update its cache.
826 WillBeHeapVector
<RefPtrWillBeMember
<Element
>> elements
;
827 getNamedElements(name
, elements
);
828 if (elements
.isEmpty())
832 // Second call may return different results from the first call,
833 // but if the first the size cannot be zero.
834 WillBeHeapVector
<RefPtrWillBeMember
<Element
>> elements
;
835 getNamedElements(name
, elements
);
836 ASSERT(!elements
.isEmpty());
838 if (elements
.size() == 1) {
839 returnValue
.setElement(elements
.at(0));
843 bool onlyMatchImg
= !elements
.isEmpty() && isHTMLImageElement(*elements
.first());
844 returnValue
.setRadioNodeList(radioNodeList(name
, onlyMatchImg
));
847 void HTMLFormElement::setDemoted(bool demoted
)
850 UseCounter::count(document(), UseCounter::DemotedFormElement
);
851 m_wasDemoted
= demoted
;