Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/StringBuilder.h>
28#include <LibWeb/CSS/Length.h>
29#include <LibWeb/CSS/PropertyID.h>
30#include <LibWeb/CSS/StyleResolver.h>
31#include <LibWeb/DOM/Document.h>
32#include <LibWeb/DOM/DocumentFragment.h>
33#include <LibWeb/DOM/Element.h>
34#include <LibWeb/DOM/Text.h>
35#include <LibWeb/Dump.h>
36#include <LibWeb/Layout/LayoutBlock.h>
37#include <LibWeb/Layout/LayoutInline.h>
38#include <LibWeb/Layout/LayoutListItem.h>
39#include <LibWeb/Layout/LayoutTable.h>
40#include <LibWeb/Layout/LayoutTableCell.h>
41#include <LibWeb/Layout/LayoutTableRow.h>
42#include <LibWeb/Layout/LayoutTreeBuilder.h>
43#include <LibWeb/Parser/HTMLParser.h>
44
45namespace Web {
46
47Element::Element(Document& document, const FlyString& tag_name)
48 : ParentNode(document, NodeType::ELEMENT_NODE)
49 , m_tag_name(tag_name)
50{
51}
52
53Element::~Element()
54{
55}
56
57Attribute* Element::find_attribute(const FlyString& name)
58{
59 for (auto& attribute : m_attributes) {
60 if (attribute.name() == name)
61 return &attribute;
62 }
63 return nullptr;
64}
65
66const Attribute* Element::find_attribute(const FlyString& name) const
67{
68 for (auto& attribute : m_attributes) {
69 if (attribute.name() == name)
70 return &attribute;
71 }
72 return nullptr;
73}
74
75String Element::attribute(const FlyString& name) const
76{
77 if (auto* attribute = find_attribute(name))
78 return attribute->value();
79 return {};
80}
81
82void Element::set_attribute(const FlyString& name, const String& value)
83{
84 if (auto* attribute = find_attribute(name))
85 attribute->set_value(value);
86 else
87 m_attributes.empend(name, value);
88
89 parse_attribute(name, value);
90}
91
92void Element::set_attributes(Vector<Attribute>&& attributes)
93{
94 m_attributes = move(attributes);
95
96 for (auto& attribute : m_attributes)
97 parse_attribute(attribute.name(), attribute.value());
98}
99
100bool Element::has_class(const StringView& class_name) const
101{
102 auto value = attribute("class");
103 if (value.is_empty())
104 return false;
105 auto parts = value.split_view(' ');
106 for (auto& part : parts) {
107 if (part == class_name)
108 return true;
109 }
110 return false;
111}
112
113RefPtr<LayoutNode> Element::create_layout_node(const StyleProperties* parent_style) const
114{
115 auto style = document().style_resolver().resolve_style(*this, parent_style);
116 const_cast<Element&>(*this).m_resolved_style = style;
117 auto display = style->string_or_fallback(CSS::PropertyID::Display, "inline");
118
119 if (display == "none")
120 return nullptr;
121 if (display == "block")
122 return adopt(*new LayoutBlock(this, move(style)));
123 if (display == "inline")
124 return adopt(*new LayoutInline(*this, move(style)));
125 if (display == "list-item")
126 return adopt(*new LayoutListItem(*this, move(style)));
127 if (display == "table")
128 return adopt(*new LayoutTable(*this, move(style)));
129 if (display == "table-row")
130 return adopt(*new LayoutTableRow(*this, move(style)));
131 if (display == "table-cell")
132 return adopt(*new LayoutTableCell(*this, move(style)));
133 if (display == "inline-block")
134 return adopt(*new LayoutBlock(this, move(style)));
135
136 ASSERT_NOT_REACHED();
137}
138
139void Element::parse_attribute(const FlyString&, const String&)
140{
141}
142
143enum class StyleDifference {
144 None,
145 NeedsRepaint,
146 NeedsRelayout,
147};
148
149static StyleDifference compute_style_difference(const StyleProperties& old_style, const StyleProperties& new_style, const Document& document)
150{
151 if (old_style == new_style)
152 return StyleDifference::None;
153
154 bool needs_repaint = false;
155 bool needs_relayout = false;
156
157 if (new_style.color_or_fallback(CSS::PropertyID::Color, document, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::Color, document, Color::Black))
158 needs_repaint = true;
159 else if (new_style.color_or_fallback(CSS::PropertyID::BackgroundColor, document, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::BackgroundColor, document, Color::Black))
160 needs_repaint = true;
161
162 if (needs_relayout)
163 return StyleDifference::NeedsRelayout;
164 if (needs_repaint)
165 return StyleDifference::NeedsRepaint;
166 return StyleDifference::None;
167}
168
169void Element::recompute_style()
170{
171 set_needs_style_update(false);
172 ASSERT(parent());
173 auto* parent_layout_node = parent()->layout_node();
174 if (!parent_layout_node)
175 return;
176 ASSERT(parent_layout_node);
177 auto style = document().style_resolver().resolve_style(*this, &parent_layout_node->style());
178 m_resolved_style = style;
179 if (!layout_node()) {
180 if (style->string_or_fallback(CSS::PropertyID::Display, "inline") == "none")
181 return;
182 // We need a new layout tree here!
183 LayoutTreeBuilder tree_builder;
184 tree_builder.build(*this);
185 return;
186 }
187 auto diff = compute_style_difference(layout_node()->style(), *style, document());
188 if (diff == StyleDifference::None)
189 return;
190 layout_node()->set_style(*style);
191 if (diff == StyleDifference::NeedsRelayout) {
192 ASSERT_NOT_REACHED();
193 }
194 if (diff == StyleDifference::NeedsRepaint) {
195 layout_node()->set_needs_display();
196 }
197}
198
199NonnullRefPtr<StyleProperties> Element::computed_style()
200{
201 auto properties = m_resolved_style->clone();
202 if (layout_node() && layout_node()->has_style()) {
203 CSS::PropertyID box_model_metrics[] = {
204 CSS::PropertyID::MarginTop,
205 CSS::PropertyID::MarginBottom,
206 CSS::PropertyID::MarginLeft,
207 CSS::PropertyID::MarginRight,
208 CSS::PropertyID::PaddingTop,
209 CSS::PropertyID::PaddingBottom,
210 CSS::PropertyID::PaddingLeft,
211 CSS::PropertyID::PaddingRight,
212 CSS::PropertyID::BorderTopWidth,
213 CSS::PropertyID::BorderBottomWidth,
214 CSS::PropertyID::BorderLeftWidth,
215 CSS::PropertyID::BorderRightWidth,
216 };
217 for (CSS::PropertyID id : box_model_metrics) {
218 auto prop = layout_node()->style().property(id);
219 if (prop.has_value())
220 properties->set_property(id, prop.value());
221 }
222 }
223 return properties;
224}
225
226void Element::set_inner_html(StringView markup)
227{
228 auto fragment = parse_html_fragment(document(), markup);
229 remove_all_children();
230 if (!fragment)
231 return;
232 while (RefPtr<Node> child = fragment->first_child()) {
233 fragment->remove_child(*child);
234 append_child(*child);
235 }
236
237 set_needs_style_update(true);
238 document().schedule_style_update();
239 document().invalidate_layout();
240}
241
242String Element::inner_html() const
243{
244 StringBuilder builder;
245
246 Function<void(const Node&)> recurse = [&](auto& node) {
247 for (auto* child = node.first_child(); child; child = child->next_sibling()) {
248 if (child->is_element()) {
249 builder.append('<');
250 builder.append(to<Element>(*child).tag_name());
251 builder.append('>');
252
253 recurse(*child);
254
255 builder.append("</");
256 builder.append(to<Element>(*child).tag_name());
257 builder.append('>');
258 }
259 if (child->is_text()) {
260 builder.append(to<Text>(*child).data());
261 }
262 }
263 };
264 recurse(*this);
265
266 return builder.to_string();
267}
268
269}