Serenity Operating System
at hosted 269 lines 8.8 kB view raw
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}