Serenity Operating System
at master 287 lines 9.9 kB view raw
1/* 2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/StringBuilder.h> 9#include <LibWeb/DOM/Document.h> 10#include <LibWeb/DOM/Event.h> 11#include <LibWeb/HTML/BrowsingContext.h> 12#include <LibWeb/HTML/EventNames.h> 13#include <LibWeb/HTML/HTMLButtonElement.h> 14#include <LibWeb/HTML/HTMLFieldSetElement.h> 15#include <LibWeb/HTML/HTMLFormElement.h> 16#include <LibWeb/HTML/HTMLInputElement.h> 17#include <LibWeb/HTML/HTMLObjectElement.h> 18#include <LibWeb/HTML/HTMLOutputElement.h> 19#include <LibWeb/HTML/HTMLSelectElement.h> 20#include <LibWeb/HTML/HTMLTextAreaElement.h> 21#include <LibWeb/HTML/SubmitEvent.h> 22#include <LibWeb/Page/Page.h> 23#include <LibWeb/URL/URL.h> 24 25namespace Web::HTML { 26 27HTMLFormElement::HTMLFormElement(DOM::Document& document, DOM::QualifiedName qualified_name) 28 : HTMLElement(document, move(qualified_name)) 29{ 30} 31 32HTMLFormElement::~HTMLFormElement() = default; 33 34JS::ThrowCompletionOr<void> HTMLFormElement::initialize(JS::Realm& realm) 35{ 36 MUST_OR_THROW_OOM(Base::initialize(realm)); 37 set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLFormElementPrototype>(realm, "HTMLFormElement")); 38 39 return {}; 40} 41 42void HTMLFormElement::visit_edges(Cell::Visitor& visitor) 43{ 44 Base::visit_edges(visitor); 45 visitor.visit(m_elements); 46 for (auto& element : m_associated_elements) 47 visitor.visit(element.ptr()); 48} 49 50ErrorOr<void> HTMLFormElement::submit_form(JS::GCPtr<HTMLElement> submitter, bool from_submit_binding) 51{ 52 if (cannot_navigate()) 53 return {}; 54 55 if (action().is_null()) { 56 dbgln("Unsupported form action ''"); 57 return {}; 58 } 59 60 auto effective_method = method().to_lowercase(); 61 62 if (effective_method == "dialog") { 63 dbgln("Failed to submit form: Unsupported form method '{}'", method()); 64 return {}; 65 } 66 67 if (effective_method != "get" && effective_method != "post") { 68 effective_method = "get"; 69 } 70 71 if (!from_submit_binding) { 72 if (m_firing_submission_events) 73 return {}; 74 75 m_firing_submission_events = true; 76 77 // FIXME: If the submitter element's no-validate state is false... 78 79 JS::GCPtr<HTMLElement> submitter_button; 80 81 if (submitter != this) 82 submitter_button = submitter; 83 84 SubmitEventInit event_init {}; 85 event_init.submitter = submitter_button; 86 auto submit_event = SubmitEvent::create(realm(), String::from_deprecated_string(EventNames::submit).release_value_but_fixme_should_propagate_errors(), event_init).release_value_but_fixme_should_propagate_errors(); 87 submit_event->set_bubbles(true); 88 submit_event->set_cancelable(true); 89 bool continue_ = dispatch_event(*submit_event); 90 91 m_firing_submission_events = false; 92 93 if (!continue_) 94 return {}; 95 96 // This is checked again because arbitrary JS may have run when handling submit, 97 // which may have changed the result. 98 if (cannot_navigate()) 99 return {}; 100 } 101 102 AK::URL url(document().parse_url(action())); 103 104 if (!url.is_valid()) { 105 dbgln("Failed to submit form: Invalid URL: {}", action()); 106 return {}; 107 } 108 109 if (url.scheme() == "file") { 110 if (document().url().scheme() != "file") { 111 dbgln("Failed to submit form: Security violation: {} may not submit to {}", document().url(), url); 112 return {}; 113 } 114 if (effective_method != "get") { 115 dbgln("Failed to submit form: Unsupported form method '{}' for URL: {}", method(), url); 116 return {}; 117 } 118 } else if (url.scheme() != "http" && url.scheme() != "https") { 119 dbgln("Failed to submit form: Unsupported protocol for URL: {}", url); 120 return {}; 121 } 122 123 Vector<URL::QueryParam> parameters; 124 125 for_each_in_inclusive_subtree_of_type<HTMLInputElement>([&](auto& input) { 126 if (!input.name().is_null() && (input.type() != "submit" || &input == submitter)) { 127 auto name = String::from_deprecated_string(input.name()).release_value_but_fixme_should_propagate_errors(); 128 auto value = String::from_deprecated_string(input.value()).release_value_but_fixme_should_propagate_errors(); 129 parameters.append({ move(name), move(value) }); 130 } 131 return IterationDecision::Continue; 132 }); 133 134 if (effective_method == "get") { 135 auto url_encoded_parameters = TRY(url_encode(parameters, AK::URL::PercentEncodeSet::ApplicationXWWWFormUrlencoded)).to_deprecated_string(); 136 url.set_query(move(url_encoded_parameters)); 137 } 138 139 LoadRequest request = LoadRequest::create_for_url_on_page(url, document().page()); 140 141 if (effective_method == "post") { 142 auto url_encoded_parameters = TRY(url_encode(parameters, AK::URL::PercentEncodeSet::ApplicationXWWWFormUrlencoded)); 143 auto body = TRY(ByteBuffer::copy(url_encoded_parameters.bytes())); 144 request.set_method("POST"); 145 request.set_header("Content-Type", "application/x-www-form-urlencoded"); 146 request.set_body(move(body)); 147 } 148 149 if (auto* page = document().page()) 150 page->load(request); 151 152 return {}; 153} 154 155// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#resetting-a-form 156void HTMLFormElement::reset_form() 157{ 158 // 1. Let reset be the result of firing an event named reset at form, with the bubbles and cancelable attributes initialized to true. 159 auto reset_event = DOM::Event::create(realm(), HTML::EventNames::reset).release_value_but_fixme_should_propagate_errors(); 160 reset_event->set_bubbles(true); 161 reset_event->set_cancelable(true); 162 163 bool reset = dispatch_event(reset_event); 164 165 // 2. If reset is true, then invoke the reset algorithm of each resettable element whose form owner is form. 166 if (reset) { 167 for (auto element : m_associated_elements) { 168 VERIFY(is<FormAssociatedElement>(*element)); 169 auto& form_associated_element = dynamic_cast<FormAssociatedElement&>(*element); 170 if (form_associated_element.is_resettable()) 171 form_associated_element.reset_algorithm(); 172 } 173 } 174} 175 176WebIDL::ExceptionOr<void> HTMLFormElement::submit() 177{ 178 auto& vm = realm().vm(); 179 180 TRY_OR_THROW_OOM(vm, submit_form(this, true)); 181 return {}; 182} 183 184// https://html.spec.whatwg.org/multipage/forms.html#dom-form-reset 185void HTMLFormElement::reset() 186{ 187 // 1. If the form element is marked as locked for reset, then return. 188 if (m_locked_for_reset) 189 return; 190 191 // 2. Mark the form element as locked for reset. 192 m_locked_for_reset = true; 193 194 // 3. Reset the form element. 195 reset_form(); 196 197 // 4. Unmark the form element as locked for reset. 198 m_locked_for_reset = false; 199} 200 201void HTMLFormElement::add_associated_element(Badge<FormAssociatedElement>, HTMLElement& element) 202{ 203 m_associated_elements.append(element); 204} 205 206void HTMLFormElement::remove_associated_element(Badge<FormAssociatedElement>, HTMLElement& element) 207{ 208 m_associated_elements.remove_first_matching([&](auto& entry) { return entry.ptr() == &element; }); 209} 210 211// https://html.spec.whatwg.org/#dom-fs-action 212DeprecatedString HTMLFormElement::action() const 213{ 214 auto value = attribute(HTML::AttributeNames::action); 215 216 // Return the current URL if the action attribute is null or an empty string 217 if (value.is_null() || value.is_empty()) { 218 return document().url().to_deprecated_string(); 219 } 220 221 return value; 222} 223 224static bool is_form_control(DOM::Element const& element) 225{ 226 if (is<HTMLButtonElement>(element) 227 || is<HTMLFieldSetElement>(element) 228 || is<HTMLObjectElement>(element) 229 || is<HTMLOutputElement>(element) 230 || is<HTMLSelectElement>(element) 231 || is<HTMLTextAreaElement>(element)) { 232 return true; 233 } 234 235 if (is<HTMLInputElement>(element) 236 && !element.get_attribute(HTML::AttributeNames::type).equals_ignoring_ascii_case("image"sv)) { 237 return true; 238 } 239 240 return false; 241} 242 243// https://html.spec.whatwg.org/multipage/forms.html#dom-form-elements 244JS::NonnullGCPtr<DOM::HTMLCollection> HTMLFormElement::elements() const 245{ 246 if (!m_elements) { 247 m_elements = DOM::HTMLCollection::create(const_cast<HTMLFormElement&>(*this), [](Element const& element) { 248 return is_form_control(element); 249 }).release_value_but_fixme_should_propagate_errors(); 250 } 251 return *m_elements; 252} 253 254// https://html.spec.whatwg.org/multipage/forms.html#dom-form-length 255unsigned HTMLFormElement::length() const 256{ 257 // The length IDL attribute must return the number of nodes represented by the elements collection. 258 return elements()->length(); 259} 260 261// https://html.spec.whatwg.org/multipage/forms.html#category-submit 262ErrorOr<Vector<JS::NonnullGCPtr<DOM::Element>>> HTMLFormElement::get_submittable_elements() 263{ 264 Vector<JS::NonnullGCPtr<DOM::Element>> submittable_elements = {}; 265 for (size_t i = 0; i < elements()->length(); i++) { 266 auto* element = elements()->item(i); 267 TRY(populate_vector_with_submittable_elements_in_tree_order(*element, submittable_elements)); 268 } 269 return submittable_elements; 270} 271 272ErrorOr<void> HTMLFormElement::populate_vector_with_submittable_elements_in_tree_order(JS::NonnullGCPtr<DOM::Element> element, Vector<JS::NonnullGCPtr<DOM::Element>>& elements) 273{ 274 if (auto* form_associated_element = dynamic_cast<HTML::FormAssociatedElement*>(element.ptr())) { 275 if (form_associated_element->is_submittable()) 276 TRY(elements.try_append(element)); 277 } 278 279 for (size_t i = 0; i < element->children()->length(); i++) { 280 auto* child = element->children()->item(i); 281 TRY(populate_vector_with_submittable_elements_in_tree_order(*child, elements)); 282 } 283 284 return {}; 285} 286 287}