mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 12:05:15 +00:00
LibHTML: Implement basic <form> and <input> element support
This patch adds "submit" inputs and default (text box) inputs, as well as form elements that can be submitted. Layout of input elements is implemented via a new LayoutWidget class that allows you to put an arbitrary GWidget in the layout tree. At the moment, the DOM node sets the initial size of the LayoutWidget, and then the positioning is done by the normal layout algorithm. We also now support submitting a <form method="GET">, which does a full replacing load with a URL based on the form's action + a query string built from the name/value of input elements within the submitted form. This is pretty neat! :^)
This commit is contained in:
parent
a91c17c0eb
commit
6d1c4ae5a9
Notes:
sideshowbarker
2024-07-19 11:04:51 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/6d1c4ae5a9b
11 changed files with 229 additions and 0 deletions
10
Base/home/anon/www/form.html
Normal file
10
Base/home/anon/www/form.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE>
|
||||
<html>
|
||||
<head><title>Form</title></head>
|
||||
<body>
|
||||
<form method="GET" action="form.html">
|
||||
<input type="submit" value="cool" name="cyber">
|
||||
<input type="text" value="hello_friends" name="greeting">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -24,6 +24,7 @@ h1 {
|
|||
<p>Some small test pages:</p>
|
||||
<ul>
|
||||
<li><a href="small.html">small</a></li>
|
||||
<li><a href="form.html">form</a></li>
|
||||
<li><a href="borders.html">borders</a></li>
|
||||
<li><a href="css.html">css</a></li>
|
||||
<li><a href="acid1.html">acid1</a></li>
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
#include <LibHTML/DOM/HTMLBlinkElement.h>
|
||||
#include <LibHTML/DOM/HTMLBodyElement.h>
|
||||
#include <LibHTML/DOM/HTMLFontElement.h>
|
||||
#include <LibHTML/DOM/HTMLFormElement.h>
|
||||
#include <LibHTML/DOM/HTMLHRElement.h>
|
||||
#include <LibHTML/DOM/HTMLHeadElement.h>
|
||||
#include <LibHTML/DOM/HTMLHeadingElement.h>
|
||||
#include <LibHTML/DOM/HTMLHtmlElement.h>
|
||||
#include <LibHTML/DOM/HTMLImageElement.h>
|
||||
#include <LibHTML/DOM/HTMLInputElement.h>
|
||||
#include <LibHTML/DOM/HTMLLinkElement.h>
|
||||
#include <LibHTML/DOM/HTMLStyleElement.h>
|
||||
#include <LibHTML/DOM/HTMLTitleElement.h>
|
||||
|
@ -38,6 +40,10 @@ NonnullRefPtr<Element> create_element(Document& document, const String& tag_name
|
|||
return adopt(*new HTMLImageElement(document, lowercase_tag_name));
|
||||
if (lowercase_tag_name == "blink")
|
||||
return adopt(*new HTMLBlinkElement(document, lowercase_tag_name));
|
||||
if (lowercase_tag_name == "form")
|
||||
return adopt(*new HTMLFormElement(document, lowercase_tag_name));
|
||||
if (lowercase_tag_name == "input")
|
||||
return adopt(*new HTMLInputElement(document, lowercase_tag_name));
|
||||
if (lowercase_tag_name == "br")
|
||||
return adopt(*new HTMLBRElement(document, lowercase_tag_name));
|
||||
if (lowercase_tag_name == "h1"
|
||||
|
|
58
Libraries/LibHTML/DOM/HTMLFormElement.cpp
Normal file
58
Libraries/LibHTML/DOM/HTMLFormElement.cpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <LibHTML/DOM/HTMLFormElement.h>
|
||||
#include <LibHTML/DOM/HTMLInputElement.h>
|
||||
#include <LibHTML/Frame.h>
|
||||
#include <LibHTML/HtmlView.h>
|
||||
|
||||
HTMLFormElement::HTMLFormElement(Document& document, const String& tag_name)
|
||||
: HTMLElement(document, tag_name)
|
||||
{
|
||||
}
|
||||
|
||||
HTMLFormElement::~HTMLFormElement()
|
||||
{
|
||||
}
|
||||
|
||||
void HTMLFormElement::submit()
|
||||
{
|
||||
if (action().is_null()) {
|
||||
dbg() << "Unsupported form action ''";
|
||||
return;
|
||||
}
|
||||
|
||||
if (method().to_lowercase() != "get") {
|
||||
dbg() << "Unsupported form method '" << method() << "'";
|
||||
return;
|
||||
}
|
||||
|
||||
URL url(document().complete_url(action()));
|
||||
|
||||
struct NameAndValue {
|
||||
String name;
|
||||
String value;
|
||||
};
|
||||
|
||||
Vector<NameAndValue> parameters;
|
||||
|
||||
for_each_in_subtree([&](auto& node) {
|
||||
if (is<HTMLInputElement>(node)) {
|
||||
auto& input = to<HTMLInputElement>(node);
|
||||
if (!input.name().is_null())
|
||||
parameters.append({ input.name(), input.value() });
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
StringBuilder builder;
|
||||
for (int i = 0; i < parameters.size(); ++i) {
|
||||
builder.append(parameters[i].name);
|
||||
builder.append('=');
|
||||
builder.append(parameters[i].value);
|
||||
if (i != parameters.size() - 1)
|
||||
builder.append('&');
|
||||
}
|
||||
url.set_query(builder.to_string());
|
||||
|
||||
// FIXME: We shouldn't let the form just do this willy-nilly.
|
||||
document().frame()->html_view()->load(url);
|
||||
}
|
20
Libraries/LibHTML/DOM/HTMLFormElement.h
Normal file
20
Libraries/LibHTML/DOM/HTMLFormElement.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibHTML/DOM/HTMLElement.h>
|
||||
|
||||
class HTMLFormElement : public HTMLElement {
|
||||
public:
|
||||
HTMLFormElement(Document&, const String& tag_name);
|
||||
virtual ~HTMLFormElement() override;
|
||||
|
||||
String action() const { return attribute("action"); }
|
||||
String method() const { return attribute("method"); }
|
||||
|
||||
void submit();
|
||||
};
|
||||
|
||||
template<>
|
||||
inline bool is<HTMLFormElement>(const Node& node)
|
||||
{
|
||||
return is<Element>(node) && to<Element>(node).tag_name().to_lowercase() == "form";
|
||||
}
|
51
Libraries/LibHTML/DOM/HTMLInputElement.cpp
Normal file
51
Libraries/LibHTML/DOM/HTMLInputElement.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include <LibGUI/GButton.h>
|
||||
#include <LibGUI/GTextBox.h>
|
||||
#include <LibHTML/DOM/Document.h>
|
||||
#include <LibHTML/DOM/HTMLFormElement.h>
|
||||
#include <LibHTML/DOM/HTMLInputElement.h>
|
||||
#include <LibHTML/Frame.h>
|
||||
#include <LibHTML/HtmlView.h>
|
||||
#include <LibHTML/Layout/LayoutWidget.h>
|
||||
|
||||
HTMLInputElement::HTMLInputElement(Document& document, const String& tag_name)
|
||||
: HTMLElement(document, tag_name)
|
||||
{
|
||||
}
|
||||
|
||||
HTMLInputElement::~HTMLInputElement()
|
||||
{
|
||||
}
|
||||
|
||||
RefPtr<LayoutNode> HTMLInputElement::create_layout_node(const StyleProperties*) const
|
||||
{
|
||||
ASSERT(document().frame());
|
||||
auto& frame = *document().frame();
|
||||
ASSERT(frame.html_view());
|
||||
auto& html_view = const_cast<HtmlView&>(*frame.html_view());
|
||||
|
||||
RefPtr<GWidget> widget;
|
||||
if (type() == "submit") {
|
||||
auto button = GButton::construct(value(), &html_view);
|
||||
int text_width = Font::default_font().width(value());
|
||||
button->set_relative_rect(0, 0, text_width + 20, 20);
|
||||
button->on_click = [this](auto&) {
|
||||
if (auto* form = first_ancestor_of_type<HTMLFormElement>()) {
|
||||
// FIXME: Remove this const_cast once we have a non-const first_ancestor_of_type.
|
||||
const_cast<HTMLFormElement*>(form)->submit();
|
||||
}
|
||||
};
|
||||
widget = button;
|
||||
} else {
|
||||
auto text_box = GTextBox::construct(&html_view);
|
||||
text_box->set_text(value());
|
||||
text_box->on_change = [this] {
|
||||
auto& widget = to<LayoutWidget>(layout_node())->widget();
|
||||
const_cast<HTMLInputElement*>(this)->set_attribute("value", static_cast<const GTextBox&>(widget).text());
|
||||
};
|
||||
int text_width = Font::default_font().width(value());
|
||||
text_box->set_relative_rect(0, 0, text_width + 20, 20);
|
||||
widget = text_box;
|
||||
}
|
||||
|
||||
return adopt(*new LayoutWidget(*this, *widget));
|
||||
}
|
21
Libraries/LibHTML/DOM/HTMLInputElement.h
Normal file
21
Libraries/LibHTML/DOM/HTMLInputElement.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibHTML/DOM/HTMLElement.h>
|
||||
|
||||
class HTMLInputElement : public HTMLElement {
|
||||
public:
|
||||
HTMLInputElement(Document&, const String& tag_name);
|
||||
virtual ~HTMLInputElement() override;
|
||||
|
||||
virtual RefPtr<LayoutNode> create_layout_node(const StyleProperties* parent_style) const override;
|
||||
|
||||
String type() const { return attribute("type"); }
|
||||
String value() const { return attribute("value"); }
|
||||
String name() const { return attribute("name"); }
|
||||
};
|
||||
|
||||
template<>
|
||||
inline bool is<HTMLInputElement>(const Node& node)
|
||||
{
|
||||
return is<Element>(node) && to<Element>(node).tag_name().to_lowercase() == "input";
|
||||
}
|
|
@ -57,6 +57,7 @@ public:
|
|||
virtual bool is_text() const { return false; }
|
||||
virtual bool is_block() const { return false; }
|
||||
virtual bool is_replaced() const { return false; }
|
||||
virtual bool is_widget() const { return false; }
|
||||
virtual bool is_box() const { return false; }
|
||||
virtual bool is_table() const { return false; }
|
||||
virtual bool is_table_row() const { return false; }
|
||||
|
|
28
Libraries/LibHTML/Layout/LayoutWidget.cpp
Normal file
28
Libraries/LibHTML/Layout/LayoutWidget.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include <LibDraw/Font.h>
|
||||
#include <LibDraw/StylePainter.h>
|
||||
#include <LibGUI/GPainter.h>
|
||||
#include <LibGUI/GWidget.h>
|
||||
#include <LibHTML/Layout/LayoutWidget.h>
|
||||
|
||||
LayoutWidget::LayoutWidget(const Element& element, GWidget& widget)
|
||||
: LayoutReplaced(element, StyleProperties::create())
|
||||
, m_widget(widget)
|
||||
{
|
||||
}
|
||||
|
||||
LayoutWidget::~LayoutWidget()
|
||||
{
|
||||
widget().remove_from_parent();
|
||||
}
|
||||
|
||||
void LayoutWidget::layout()
|
||||
{
|
||||
rect().set_size(FloatSize(widget().width(), widget().height()));
|
||||
LayoutReplaced::layout();
|
||||
widget().move_to(rect().x(), rect().y());
|
||||
}
|
||||
|
||||
void LayoutWidget::render(RenderingContext& context)
|
||||
{
|
||||
LayoutReplaced::render(context);
|
||||
}
|
30
Libraries/LibHTML/Layout/LayoutWidget.h
Normal file
30
Libraries/LibHTML/Layout/LayoutWidget.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibHTML/Layout/LayoutReplaced.h>
|
||||
|
||||
class GWidget;
|
||||
|
||||
class LayoutWidget : public LayoutReplaced {
|
||||
public:
|
||||
LayoutWidget(const Element&, GWidget&);
|
||||
virtual ~LayoutWidget() override;
|
||||
|
||||
virtual void layout() override;
|
||||
virtual void render(RenderingContext&) override;
|
||||
|
||||
GWidget& widget() { return m_widget; }
|
||||
const GWidget& widget() const { return m_widget; }
|
||||
|
||||
virtual bool is_widget() const final { return true; }
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "LayoutWidget"; }
|
||||
|
||||
NonnullRefPtr<GWidget> m_widget;
|
||||
};
|
||||
|
||||
template<>
|
||||
inline bool is<LayoutWidget>(const LayoutNode& node)
|
||||
{
|
||||
return node.is_widget();
|
||||
}
|
|
@ -27,11 +27,13 @@ LIBHTML_OBJS = \
|
|||
DOM/HTMLBodyElement.o \
|
||||
DOM/HTMLElement.o \
|
||||
DOM/HTMLFontElement.o \
|
||||
DOM/HTMLFormElement.o \
|
||||
DOM/HTMLHRElement.o \
|
||||
DOM/HTMLHeadElement.o \
|
||||
DOM/HTMLHeadingElement.o \
|
||||
DOM/HTMLHtmlElement.o \
|
||||
DOM/HTMLImageElement.o \
|
||||
DOM/HTMLInputElement.o \
|
||||
DOM/HTMLLinkElement.o \
|
||||
DOM/HTMLStyleElement.o \
|
||||
DOM/HTMLTitleElement.o \
|
||||
|
@ -59,6 +61,7 @@ LIBHTML_OBJS = \
|
|||
Layout/LayoutTableRow.o \
|
||||
Layout/LayoutText.o \
|
||||
Layout/LayoutTreeBuilder.o \
|
||||
Layout/LayoutWidget.o \
|
||||
Layout/LineBox.o \
|
||||
Layout/LineBoxFragment.o \
|
||||
Parser/CSSParser.o \
|
||||
|
|
Loading…
Add table
Reference in a new issue