blob: 05cff133e18618b95c31ac062d39edfe4a56ab2f [file] [log] [blame]
/*
* 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.
* (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 "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include <limits>
#include "base/auto_reset.h"
#include "third_party/blink/public/common/security_context/insecure_request_policy.h"
#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/radio_node_list_or_element.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_submit_event_init.h"
#include "third_party/blink/renderer/core/dom/attribute.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
#include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/remote_frame.h"
#include "third_party/blink/renderer/core/html/custom/custom_element.h"
#include "third_party/blink/renderer/core/html/custom/element_internals.h"
#include "third_party/blink/renderer/core/html/forms/form_controller.h"
#include "third_party/blink/renderer/core/html/forms/form_data.h"
#include "third_party/blink/renderer/core/html/forms/form_data_event.h"
#include "third_party/blink/renderer/core/html/forms/html_form_controls_collection.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/radio_node_list.h"
#include "third_party/blink/renderer/core/html/forms/submit_event.h"
#include "third_party/blink/renderer/core/html/html_collection.h"
#include "third_party/blink/renderer/core/html/html_dialog_element.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html/html_object_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/loader/form_submission.h"
#include "third_party/blink/renderer/core/loader/mixed_content_checker.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
namespace blink {
HTMLFormElement::HTMLFormElement(Document& document)
: HTMLElement(html_names::kFormTag, document),
listed_elements_are_dirty_(false),
image_elements_are_dirty_(false),
has_elements_associated_by_parser_(false),
has_elements_associated_by_form_attribute_(false),
did_finish_parsing_children_(false),
is_in_reset_function_(false) {
static unsigned next_nique_renderer_form_id = 0;
unique_renderer_form_id_ = next_nique_renderer_form_id++;
UseCounter::Count(document, WebFeature::kFormElement);
}
HTMLFormElement::~HTMLFormElement() = default;
void HTMLFormElement::Trace(Visitor* visitor) const {
visitor->Trace(past_names_map_);
visitor->Trace(radio_button_group_scope_);
visitor->Trace(listed_elements_);
visitor->Trace(image_elements_);
HTMLElement::Trace(visitor);
}
bool HTMLFormElement::MatchesValidityPseudoClasses() const {
return true;
}
bool HTMLFormElement::IsValidElement() {
for (const auto& element : ListedElements()) {
if (!element->IsNotCandidateOrValid())
return false;
}
return true;
}
Node::InsertionNotificationRequest HTMLFormElement::InsertedInto(
ContainerNode& insertion_point) {
HTMLElement::InsertedInto(insertion_point);
LogAddElementIfIsolatedWorldAndInDocument("form", html_names::kMethodAttr,
html_names::kActionAttr);
if (insertion_point.isConnected())
GetDocument().DidAssociateFormControl(this);
return kInsertionDone;
}
template <class T>
void NotifyFormRemovedFromTree(const T& elements, Node& root) {
for (const auto& element : elements)
element->FormRemovedFromTree(root);
}
void HTMLFormElement::RemovedFrom(ContainerNode& insertion_point) {
// We don't need to take care of form association by 'form' content
// attribute becuse IdTargetObserver handles it.
if (has_elements_associated_by_parser_) {
Node& root = NodeTraversal::HighestAncestorOrSelf(*this);
if (!listed_elements_are_dirty_) {
ListedElement::List elements(ListedElements());
NotifyFormRemovedFromTree(elements, root);
} else {
ListedElement::List elements;
CollectListedElements(
NodeTraversal::HighestAncestorOrSelf(insertion_point), elements);
NotifyFormRemovedFromTree(elements, root);
CollectListedElements(root, elements);
NotifyFormRemovedFromTree(elements, root);
}
if (!image_elements_are_dirty_) {
HeapVector<Member<HTMLImageElement>> images(ImageElements());
NotifyFormRemovedFromTree(images, root);
} else {
HeapVector<Member<HTMLImageElement>> images;
CollectImageElements(
NodeTraversal::HighestAncestorOrSelf(insertion_point), images);
NotifyFormRemovedFromTree(images, root);
CollectImageElements(root, images);
NotifyFormRemovedFromTree(images, root);
}
}
GetDocument().GetFormController().WillDeleteForm(this);
HTMLElement::RemovedFrom(insertion_point);
}
void HTMLFormElement::HandleLocalEvents(Event& event) {
Node* target_node = event.target()->ToNode();
if (event.eventPhase() != Event::kCapturingPhase && target_node &&
target_node != this &&
(event.type() == event_type_names::kSubmit ||
event.type() == event_type_names::kReset)) {
event.stopPropagation();
return;
}
HTMLElement::HandleLocalEvents(event);
}
unsigned HTMLFormElement::length() const {
unsigned len = 0;
for (const auto& element : ListedElements()) {
if (element->IsEnumeratable())
++len;
}
return len;
}
HTMLElement* HTMLFormElement::item(unsigned index) {
return elements()->item(index);
}
void HTMLFormElement::SubmitImplicitly(const Event& event,
bool from_implicit_submission_trigger) {
int submission_trigger_count = 0;
bool seen_default_button = false;
for (ListedElement* element : ListedElements()) {
auto* control = DynamicTo<HTMLFormControlElement>(element);
if (!control)
continue;
if (!seen_default_button && control->CanBeSuccessfulSubmitButton()) {
if (from_implicit_submission_trigger)
seen_default_button = true;
if (control->IsSuccessfulSubmitButton()) {
control->DispatchSimulatedClick(&event);
return;
}
if (from_implicit_submission_trigger) {
// Default (submit) button is not activated; no implicit submission.
return;
}
} else if (control->CanTriggerImplicitSubmission()) {
++submission_trigger_count;
}
}
if (from_implicit_submission_trigger && submission_trigger_count == 1)
PrepareForSubmission(&event, nullptr);
}
bool HTMLFormElement::ValidateInteractively() {
UseCounter::Count(GetDocument(), WebFeature::kFormValidationStarted);
for (const auto& element : ListedElements())
element->HideVisibleValidationMessage();
ListedElement::List unhandled_invalid_controls;
if (!CheckInvalidControlsAndCollectUnhandled(&unhandled_invalid_controls))
return true;
UseCounter::Count(GetDocument(),
WebFeature::kFormValidationAbortedSubmission);
// Because the form has invalid controls, we abort the form submission and
// show a validation message on a focusable form control.
// Needs to update layout now because we'd like to call isFocusable(), which
// has !layoutObject()->needsLayout() assertion.
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kFocus);
// Focus on the first focusable control and show a validation message.
for (const auto& unhandled : unhandled_invalid_controls) {
if (unhandled->ValidationAnchorOrHostIsFocusable()) {
unhandled->ShowValidationMessage();
UseCounter::Count(GetDocument(),
WebFeature::kFormValidationShowedMessage);
break;
}
}
// Warn about all of unfocusable controls.
if (GetDocument().GetFrame()) {
for (const auto& unhandled : unhandled_invalid_controls) {
if (unhandled->ValidationAnchorOrHostIsFocusable())
continue;
String message(
"An invalid form control with name='%name' is not focusable.");
message.Replace("%name", unhandled->GetName());
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kRendering,
mojom::ConsoleMessageLevel::kError, message));
}
}
return false;
}
void HTMLFormElement::PrepareForSubmission(
const Event* event,
HTMLFormControlElement* submit_button) {
LocalFrame* frame = GetDocument().GetFrame();
if (!frame || is_submitting_ || in_user_js_submit_event_)
return;
if (!isConnected()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
"Form submission canceled because the form is not connected"));
return;
}
if (GetExecutionContext()->IsSandboxed(
network::mojom::blink::WebSandboxFlags::kForms)) {
GetExecutionContext()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kSecurity,
mojom::blink::ConsoleMessageLevel::kError,
"Blocked form submission to '" + attributes_.Action() +
"' because the form's frame is sandboxed and the 'allow-forms' "
"permission is not set."));
return;
}
// https://github.com/whatwg/html/issues/2253
for (ListedElement* element : ListedElements()) {
auto* form_control_element = DynamicTo<HTMLFormControlElement>(element);
if (form_control_element && form_control_element->BlocksFormSubmission()) {
UseCounter::Count(GetDocument(),
WebFeature::kFormSubmittedWithUnclosedFormControl);
if (RuntimeEnabledFeatures::UnclosedFormControlIsInvalidEnabled()) {
String tag_name = To<HTMLFormControlElement>(element)->tagName();
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError,
"Form submission failed, as the <" + tag_name +
"> element named "
"'" +
element->GetName() +
"' was implicitly closed by reaching "
"the end of the file. Please add an explicit end tag "
"('</" +
tag_name + ">')"));
DispatchEvent(*Event::Create(event_type_names::kError));
return;
}
}
}
bool should_submit;
{
base::AutoReset<bool> submit_event_handler_scope(&in_user_js_submit_event_,
true);
bool skip_validation = !GetDocument().GetPage() || NoValidate();
if (submit_button && submit_button->FormNoValidate())
skip_validation = true;
UseCounter::Count(GetDocument(), WebFeature::kFormSubmissionStarted);
// Interactive validation must be done before dispatching the submit event.
if (!skip_validation && !ValidateInteractively()) {
should_submit = false;
} else {
frame->Client()->DispatchWillSendSubmitEvent(this);
SubmitEventInit* submit_event_init = SubmitEventInit::Create();
submit_event_init->setBubbles(true);
submit_event_init->setCancelable(true);
submit_event_init->setSubmitter(
submit_button ? &submit_button->ToHTMLElement() : nullptr);
should_submit = DispatchEvent(*MakeGarbageCollected<SubmitEvent>(
event_type_names::kSubmit, submit_event_init)) ==
DispatchEventResult::kNotCanceled;
}
}
if (should_submit) {
ScheduleFormSubmission(event, submit_button);
}
}
void HTMLFormElement::submitFromJavaScript() {
ScheduleFormSubmission(nullptr, nullptr);
}
void HTMLFormElement::requestSubmit(ExceptionState& exception_state) {
requestSubmit(nullptr, exception_state);
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-requestsubmit
void HTMLFormElement::requestSubmit(HTMLElement* submitter,
ExceptionState& exception_state) {
HTMLFormControlElement* control = nullptr;
// 1. If submitter was given, then:
if (submitter) {
// 1.1. If submitter is not a submit button, then throw a TypeError.
control = DynamicTo<HTMLFormControlElement>(submitter);
// button[type] is a subset of input[type]. So it's ok to compare button's
// type and input_type_names.
if (!control || (control->type() != input_type_names::kSubmit &&
control->type() != input_type_names::kImage)) {
exception_state.ThrowTypeError(
"The specified element is not a submit button.");
return;
}
// 1.2. If submitter's form owner is not this form element, then throw a
// "NotFoundError" DOMException.
if (control->formOwner() != this) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotFoundError,
"The specified element is not owned by this form element.");
return;
}
}
// 3. Submit this form element, from submitter.
PrepareForSubmission(nullptr, control);
}
void HTMLFormElement::SubmitDialog(FormSubmission* form_submission) {
for (Node* node = this; node; node = node->ParentOrShadowHostNode()) {
if (auto* dialog = DynamicTo<HTMLDialogElement>(*node)) {
dialog->close(form_submission->Result());
return;
}
}
}
void HTMLFormElement::ScheduleFormSubmission(
const Event* event,
HTMLFormControlElement* submit_button) {
LocalFrameView* view = GetDocument().View();
LocalFrame* frame = GetDocument().GetFrame();
if (!view || !frame || !frame->GetPage())
return;
// https://html.spec.whatwg.org/C/#form-submission-algorithm
// 2. If form document is not connected, has no associated browsing context,
// or its active sandboxing flag set has its sandboxed forms browsing
// context flag set, then abort these steps without doing anything.
if (!isConnected()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
"Form submission canceled because the form is not connected"));
return;
}
if (is_constructing_entry_list_) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
"Form submission canceled because the form is "
"constructing entry list"));
return;
}
if (is_submitting_)
return;
// Delay dispatching 'close' to dialog until done submitting.
EventQueueScope scope_for_dialog_close;
base::AutoReset<bool> submit_scope(&is_submitting_, true);
if (event && !submit_button) {
// In a case of implicit submission without a submit button, 'submit'
// event handler might add a submit button. We search for a submit
// button again.
// TODO(tkent): Do we really need to activate such submit button?
for (ListedElement* listed_element : ListedElements()) {
auto* control = DynamicTo<HTMLFormControlElement>(listed_element);
if (!control)
continue;
DCHECK(!control->IsActivatedSubmit());
if (control->IsSuccessfulSubmitButton()) {
submit_button = control;
break;
}
}
}
FormSubmission* form_submission =
FormSubmission::Create(this, attributes_, event, submit_button);
Frame* target_frame = form_submission->TargetFrame();
// 'formdata' event handlers might disconnect the form.
if (!isConnected()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
"Form submission canceled because the form is not connected"));
return;
}
if (form_submission->Method() == FormSubmission::kDialogMethod) {
SubmitDialog(form_submission);
return;
}
DCHECK(form_submission->Method() == FormSubmission::kPostMethod ||
form_submission->Method() == FormSubmission::kGetMethod);
DCHECK(form_submission->Data());
DCHECK(form_submission->Form());
if (form_submission->Action().IsEmpty())
return;
if (GetExecutionContext()->IsSandboxed(
network::mojom::blink::WebSandboxFlags::kForms)) {
// FIXME: This message should be moved off the console once a solution to
// https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
GetExecutionContext()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kSecurity,
mojom::blink::ConsoleMessageLevel::kError,
"Blocked form submission to '" +
form_submission->Action().ElidedString() +
"' because the form's frame is sandboxed and the 'allow-forms' "
"permission is not set."));
return;
}
if (!GetExecutionContext()->GetContentSecurityPolicy()->AllowFormAction(
form_submission->Action())) {
return;
}
UseCounter::Count(GetDocument(), WebFeature::kFormsSubmitted);
if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(),
form_submission->Action())) {
UseCounter::Count(GetDocument(), WebFeature::kMixedContentFormsSubmitted);
}
if (FastHasAttribute(html_names::kDisabledAttr)) {
UseCounter::Count(GetDocument(),
WebFeature::kFormDisabledAttributePresentAndSubmit);
}
if (!target_frame)
return;
if (form_submission->Action().ProtocolIsJavaScript()) {
// For javascript urls, don't post a task to execute the form submission
// because we already get another task posted for it in
// Document::ProcessJavascriptUrl. If we post two tasks, the javascript will
// be run too late according to some tests.
form_submission->Navigate();
return;
}
FrameScheduler* scheduler = GetDocument().GetFrame()->GetFrameScheduler();
if (auto* target_local_frame = DynamicTo<LocalFrame>(target_frame)) {
if (!target_local_frame->IsNavigationAllowed())
return;
// Cancel parsing if the form submission is targeted at this frame.
if (target_local_frame == GetDocument().GetFrame() &&
!form_submission->Action().ProtocolIsJavaScript()) {
target_local_frame->GetDocument()->CancelParsing();
}
// Use the target frame's frame scheduler. If we can't due to targeting a
// RemoteFrame, then use the frame scheduler from the frame this form is in.
scheduler = target_local_frame->GetFrameScheduler();
// Cancel pending javascript url navigations for the target frame. This new
// form submission should take precedence over them.
target_local_frame->GetDocument()->CancelPendingJavaScriptUrls();
// Cancel any pre-existing attempt to navigate the target frame which was
// already sent to the browser process so this form submission will take
// precedence over it.
target_local_frame->Loader().CancelClientNavigation();
}
target_frame->ScheduleFormSubmission(scheduler, form_submission);
}
FormData* HTMLFormElement::ConstructEntryList(
HTMLFormControlElement* submit_button,
const WTF::TextEncoding& encoding) {
if (is_constructing_entry_list_) {
return nullptr;
}
auto& form_data = *MakeGarbageCollected<FormData>(encoding);
base::AutoReset<bool> entry_list_scope(&is_constructing_entry_list_, true);
if (submit_button)
submit_button->SetActivatedSubmit(true);
for (ListedElement* control : ListedElements()) {
DCHECK(control);
HTMLElement& element = control->ToHTMLElement();
if (!element.IsDisabledFormControl())
control->AppendToFormData(form_data);
if (auto* input = DynamicTo<HTMLInputElement>(element)) {
if (input->type() == input_type_names::kPassword &&
!input->value().IsEmpty())
form_data.SetContainsPasswordData(true);
}
}
DispatchEvent(*MakeGarbageCollected<FormDataEvent>(form_data));
if (submit_button)
submit_button->SetActivatedSubmit(false);
return &form_data;
}
void HTMLFormElement::reset() {
LocalFrame* frame = GetDocument().GetFrame();
if (is_in_reset_function_ || !frame)
return;
is_in_reset_function_ = true;
if (DispatchEvent(*Event::CreateCancelableBubble(event_type_names::kReset)) !=
DispatchEventResult::kNotCanceled) {
is_in_reset_function_ = false;
return;
}
// Copy the element list because |reset()| implementation can update DOM
// structure.
ListedElement::List elements(ListedElements());
for (ListedElement* element : elements) {
if (auto* html_form_element = DynamicTo<HTMLFormControlElement>(element)) {
html_form_element->Reset();
} else if (element->IsElementInternals()) {
CustomElement::EnqueueFormResetCallback(element->ToHTMLElement());
}
}
is_in_reset_function_ = false;
if (frame->GetPage())
frame->GetPage()->GetChromeClient().FormElementReset(*this);
}
void HTMLFormElement::ParseAttribute(
const AttributeModificationParams& params) {
const QualifiedName& name = params.name;
if (name == html_names::kActionAttr) {
attributes_.ParseAction(params.new_value);
LogUpdateAttributeIfIsolatedWorldAndInDocument("form", params);
// If we're not upgrading insecure requests, and the new action attribute is
// pointing to an insecure "action" location from a secure page it is marked
// as "passive" mixed content.
if (GetExecutionContext() &&
(GetExecutionContext()
->GetSecurityContext()
.GetInsecureRequestPolicy() &
mojom::blink::InsecureRequestPolicy::kUpgradeInsecureRequests) !=
mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone)
return;
KURL action_url = GetDocument().CompleteURL(
attributes_.Action().IsEmpty() ? GetDocument().Url().GetString()
: attributes_.Action());
if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(),
action_url)) {
UseCounter::Count(GetDocument(), WebFeature::kMixedContentFormPresent);
}
} else if (name == html_names::kTargetAttr) {
attributes_.SetTarget(params.new_value);
} else if (name == html_names::kMethodAttr) {
attributes_.UpdateMethodType(params.new_value);
} else if (name == html_names::kEnctypeAttr) {
attributes_.UpdateEncodingType(params.new_value);
} else if (name == html_names::kAcceptCharsetAttr) {
attributes_.SetAcceptCharset(params.new_value);
} else if (name == html_names::kDisabledAttr) {
UseCounter::Count(GetDocument(), WebFeature::kFormDisabledAttributePresent);
} else {
HTMLElement::ParseAttribute(params);
}
}
void HTMLFormElement::Associate(ListedElement& e) {
listed_elements_are_dirty_ = true;
listed_elements_.clear();
if (e.ToHTMLElement().FastHasAttribute(html_names::kFormAttr))
has_elements_associated_by_form_attribute_ = true;
}
void HTMLFormElement::Disassociate(ListedElement& e) {
listed_elements_are_dirty_ = true;
listed_elements_.clear();
RemoveFromPastNamesMap(e.ToHTMLElement());
}
bool HTMLFormElement::IsURLAttribute(const Attribute& attribute) const {
return attribute.GetName() == html_names::kActionAttr ||
HTMLElement::IsURLAttribute(attribute);
}
bool HTMLFormElement::HasLegalLinkAttribute(const QualifiedName& name) const {
return name == html_names::kActionAttr ||
HTMLElement::HasLegalLinkAttribute(name);
}
void HTMLFormElement::Associate(HTMLImageElement& e) {
image_elements_are_dirty_ = true;
image_elements_.clear();
}
void HTMLFormElement::Disassociate(HTMLImageElement& e) {
image_elements_are_dirty_ = true;
image_elements_.clear();
RemoveFromPastNamesMap(e);
}
void HTMLFormElement::DidAssociateByParser() {
if (!did_finish_parsing_children_)
return;
has_elements_associated_by_parser_ = true;
UseCounter::Count(GetDocument(), WebFeature::kFormAssociationByParser);
}
HTMLFormControlsCollection* HTMLFormElement::elements() {
return EnsureCachedCollection<HTMLFormControlsCollection>(kFormControls);
}
void HTMLFormElement::CollectListedElements(
Node& root,
ListedElement::List& elements) const {
elements.clear();
for (HTMLElement& element : Traversal<HTMLElement>::StartsAfter(root)) {
ListedElement* listed_element = ListedElement::From(element);
if (listed_element && listed_element->Form() == this)
elements.push_back(listed_element);
}
}
// This function should be const conceptually. However we update some fields
// because of lazy evaluation.
const ListedElement::List& HTMLFormElement::ListedElements() const {
if (!listed_elements_are_dirty_)
return listed_elements_;
HTMLFormElement* mutable_this = const_cast<HTMLFormElement*>(this);
Node* scope = mutable_this;
if (has_elements_associated_by_parser_)
scope = &NodeTraversal::HighestAncestorOrSelf(*mutable_this);
if (isConnected() && has_elements_associated_by_form_attribute_)
scope = &GetTreeScope().RootNode();
DCHECK(scope);
CollectListedElements(*scope, mutable_this->listed_elements_);
mutable_this->listed_elements_are_dirty_ = false;
return listed_elements_;
}
void HTMLFormElement::CollectImageElements(
Node& root,
HeapVector<Member<HTMLImageElement>>& elements) {
elements.clear();
for (HTMLImageElement& image :
Traversal<HTMLImageElement>::StartsAfter(root)) {
if (image.formOwner() == this)
elements.push_back(&image);
}
}
const HeapVector<Member<HTMLImageElement>>& HTMLFormElement::ImageElements() {
if (!image_elements_are_dirty_)
return image_elements_;
CollectImageElements(has_elements_associated_by_parser_
? NodeTraversal::HighestAncestorOrSelf(*this)
: *this,
image_elements_);
image_elements_are_dirty_ = false;
return image_elements_;
}
String HTMLFormElement::GetName() const {
return GetNameAttribute();
}
bool HTMLFormElement::NoValidate() const {
return FastHasAttribute(html_names::kNovalidateAttr);
}
String HTMLFormElement::action() const {
Document& document = GetDocument();
KURL action_url = document.CompleteURL(attributes_.Action().IsEmpty()
? document.Url().GetString()
: attributes_.Action());
return action_url.GetString();
}
void HTMLFormElement::setAction(const AtomicString& value) {
setAttribute(html_names::kActionAttr, value);
}
void HTMLFormElement::setEnctype(const AtomicString& value) {
setAttribute(html_names::kEnctypeAttr, value);
}
String HTMLFormElement::method() const {
return FormSubmission::Attributes::MethodString(attributes_.Method());
}
void HTMLFormElement::setMethod(const AtomicString& value) {
setAttribute(html_names::kMethodAttr, value);
}
HTMLFormControlElement* HTMLFormElement::FindDefaultButton() const {
for (ListedElement* element : ListedElements()) {
auto* control = DynamicTo<HTMLFormControlElement>(element);
if (!control)
continue;
if (control->CanBeSuccessfulSubmitButton())
return control;
}
return nullptr;
}
bool HTMLFormElement::checkValidity() {
return !CheckInvalidControlsAndCollectUnhandled(nullptr);
}
bool HTMLFormElement::CheckInvalidControlsAndCollectUnhandled(
ListedElement::List* unhandled_invalid_controls) {
// Copy listedElements because event handlers called from
// ListedElement::checkValidity() might change listed_elements.
const ListedElement::List& listed_elements = ListedElements();
HeapVector<Member<ListedElement>> elements;
elements.ReserveCapacity(listed_elements.size());
for (ListedElement* element : listed_elements)
elements.push_back(element);
int invalid_controls_count = 0;
for (ListedElement* element : elements) {
if (element->Form() != this)
continue;
// TOOD(tkent): Virtualize checkValidity().
bool should_check_validity = false;
if (auto* html_form_element = DynamicTo<HTMLFormControlElement>(element)) {
should_check_validity = html_form_element->IsSubmittableElement();
} else if (element->IsElementInternals()) {
should_check_validity = true;
}
if (should_check_validity &&
!element->checkValidity(unhandled_invalid_controls) &&
element->Form() == this) {
++invalid_controls_count;
}
}
return invalid_controls_count;
}
bool HTMLFormElement::reportValidity() {
return ValidateInteractively();
}
Element* HTMLFormElement::ElementFromPastNamesMap(
const AtomicString& past_name) {
if (past_name.IsEmpty() || !past_names_map_)
return nullptr;
Element* element = past_names_map_->at(past_name);
#if DCHECK_IS_ON()
if (!element)
return nullptr;
SECURITY_DCHECK(To<HTMLElement>(element)->formOwner() == this);
if (IsA<HTMLImageElement>(*element)) {
SECURITY_DCHECK(ImageElements().Find(element) != kNotFound);
} else if (auto* html_image_element = DynamicTo<HTMLObjectElement>(element)) {
SECURITY_DCHECK(ListedElements().Find(html_image_element) != kNotFound);
} else {
SECURITY_DCHECK(ListedElements().Find(
To<HTMLFormControlElement>(element)) != kNotFound);
}
#endif
return element;
}
void HTMLFormElement::AddToPastNamesMap(Element* element,
const AtomicString& past_name) {
if (past_name.IsEmpty())
return;
if (!past_names_map_)
past_names_map_ = MakeGarbageCollected<PastNamesMap>();
past_names_map_->Set(past_name, element);
}
void HTMLFormElement::RemoveFromPastNamesMap(HTMLElement& element) {
if (!past_names_map_)
return;
for (auto& it : *past_names_map_) {
if (it.value == &element) {
it.value = nullptr;
// Keep looping. Single element can have multiple names.
}
}
}
void HTMLFormElement::GetNamedElements(
const AtomicString& name,
HeapVector<Member<Element>>& named_items) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem
elements()->NamedItems(name, named_items);
Element* element_from_past = ElementFromPastNamesMap(name);
if (named_items.size() && named_items.front() != element_from_past) {
AddToPastNamesMap(named_items.front().Get(), name);
} else if (element_from_past && named_items.IsEmpty()) {
named_items.push_back(element_from_past);
UseCounter::Count(GetDocument(),
WebFeature::kFormNameAccessForPastNamesMap);
}
}
bool HTMLFormElement::ShouldAutocomplete() const {
return !EqualIgnoringASCIICase(
FastGetAttribute(html_names::kAutocompleteAttr), "off");
}
void HTMLFormElement::FinishParsingChildren() {
HTMLElement::FinishParsingChildren();
GetDocument().GetFormController().RestoreControlStateIn(*this);
did_finish_parsing_children_ = true;
}
void HTMLFormElement::AnonymousNamedGetter(
const AtomicString& name,
RadioNodeListOrElement& return_value) {
// Call getNamedElements twice, first time check if it has a value
// and let HTMLFormElement update its cache.
// See issue: 867404
{
HeapVector<Member<Element>> elements;
GetNamedElements(name, elements);
if (elements.IsEmpty())
return;
}
// Second call may return different results from the first call,
// but if the first the size cannot be zero.
HeapVector<Member<Element>> elements;
GetNamedElements(name, elements);
DCHECK(!elements.IsEmpty());
bool only_match_img =
!elements.IsEmpty() && IsA<HTMLImageElement>(*elements.front());
if (only_match_img) {
UseCounter::Count(GetDocument(),
WebFeature::kFormNameAccessForImageElement);
// The following code has performance impact, but it should be small
// because <img> access via <form> name getter is rarely used.
for (auto& element : elements) {
if (IsA<HTMLImageElement>(*element) && !element->IsDescendantOf(this)) {
UseCounter::Count(
GetDocument(),
WebFeature::kFormNameAccessForNonDescendantImageElement);
break;
}
}
}
if (elements.size() == 1) {
return_value.SetElement(elements.at(0));
return;
}
return_value.SetRadioNodeList(GetRadioNodeList(name, only_match_img));
}
void HTMLFormElement::InvalidateDefaultButtonStyle() const {
for (ListedElement* control : ListedElements()) {
auto* html_form_control = DynamicTo<HTMLFormControlElement>(control);
if (!html_form_control)
continue;
if (html_form_control->CanBeSuccessfulSubmitButton()) {
html_form_control->PseudoStateChanged(CSSSelector::kPseudoDefault);
}
}
}
} // namespace blink