Element.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*
  2. * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/AnyOf.h>
  7. #include <AK/StringBuilder.h>
  8. #include <LibWeb/CSS/Parser/Parser.h>
  9. #include <LibWeb/CSS/PropertyID.h>
  10. #include <LibWeb/CSS/StyleInvalidator.h>
  11. #include <LibWeb/DOM/DOMException.h>
  12. #include <LibWeb/DOM/Document.h>
  13. #include <LibWeb/DOM/Element.h>
  14. #include <LibWeb/DOM/ExceptionOr.h>
  15. #include <LibWeb/DOM/HTMLCollection.h>
  16. #include <LibWeb/DOM/ShadowRoot.h>
  17. #include <LibWeb/DOM/Text.h>
  18. #include <LibWeb/HTML/EventLoop/EventLoop.h>
  19. #include <LibWeb/HTML/Parser/HTMLDocumentParser.h>
  20. #include <LibWeb/Layout/BlockBox.h>
  21. #include <LibWeb/Layout/InlineNode.h>
  22. #include <LibWeb/Layout/ListItemBox.h>
  23. #include <LibWeb/Layout/TableBox.h>
  24. #include <LibWeb/Layout/TableCellBox.h>
  25. #include <LibWeb/Layout/TableRowBox.h>
  26. #include <LibWeb/Layout/TableRowGroupBox.h>
  27. #include <LibWeb/Layout/TreeBuilder.h>
  28. #include <LibWeb/Namespace.h>
  29. namespace Web::DOM {
  30. Element::Element(Document& document, QualifiedName qualified_name)
  31. : ParentNode(document, NodeType::ELEMENT_NODE)
  32. , m_qualified_name(move(qualified_name))
  33. {
  34. make_html_uppercased_qualified_name();
  35. }
  36. Element::~Element()
  37. {
  38. }
  39. Attribute* Element::find_attribute(const FlyString& name)
  40. {
  41. for (auto& attribute : m_attributes) {
  42. if (attribute.name() == name)
  43. return &attribute;
  44. }
  45. return nullptr;
  46. }
  47. const Attribute* Element::find_attribute(const FlyString& name) const
  48. {
  49. for (auto& attribute : m_attributes) {
  50. if (attribute.name() == name)
  51. return &attribute;
  52. }
  53. return nullptr;
  54. }
  55. String Element::attribute(const FlyString& name) const
  56. {
  57. if (auto* attribute = find_attribute(name))
  58. return attribute->value();
  59. return {};
  60. }
  61. ExceptionOr<void> Element::set_attribute(const FlyString& name, const String& value)
  62. {
  63. // FIXME: Proper name validation
  64. if (name.is_empty())
  65. return InvalidCharacterError::create("Attribute name must not be empty");
  66. CSS::StyleInvalidator style_invalidator(document());
  67. if (auto* attribute = find_attribute(name))
  68. attribute->set_value(value);
  69. else
  70. m_attributes.empend(name, value);
  71. parse_attribute(name, value);
  72. return {};
  73. }
  74. void Element::remove_attribute(const FlyString& name)
  75. {
  76. CSS::StyleInvalidator style_invalidator(document());
  77. m_attributes.remove_first_matching([&](auto& attribute) { return attribute.name() == name; });
  78. }
  79. bool Element::has_class(const FlyString& class_name, CaseSensitivity case_sensitivity) const
  80. {
  81. return any_of(m_classes, [&](auto& it) {
  82. return case_sensitivity == CaseSensitivity::CaseSensitive
  83. ? it == class_name
  84. : it.to_lowercase() == class_name.to_lowercase();
  85. });
  86. }
  87. RefPtr<Layout::Node> Element::create_layout_node()
  88. {
  89. auto style = document().style_resolver().resolve_style(*this);
  90. const_cast<Element&>(*this).m_specified_css_values = style;
  91. auto display = style->display();
  92. if (display == CSS::Display::None)
  93. return nullptr;
  94. if (local_name() == "noscript" && document().is_scripting_enabled())
  95. return nullptr;
  96. switch (display) {
  97. case CSS::Display::None:
  98. VERIFY_NOT_REACHED();
  99. break;
  100. case CSS::Display::Block:
  101. return adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  102. case CSS::Display::Inline:
  103. if (style->float_().value_or(CSS::Float::None) != CSS::Float::None)
  104. return adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  105. return adopt_ref(*new Layout::InlineNode(document(), *this, move(style)));
  106. case CSS::Display::ListItem:
  107. return adopt_ref(*new Layout::ListItemBox(document(), *this, move(style)));
  108. case CSS::Display::Table:
  109. return adopt_ref(*new Layout::TableBox(document(), this, move(style)));
  110. case CSS::Display::TableRow:
  111. return adopt_ref(*new Layout::TableRowBox(document(), this, move(style)));
  112. case CSS::Display::TableCell:
  113. return adopt_ref(*new Layout::TableCellBox(document(), this, move(style)));
  114. case CSS::Display::TableRowGroup:
  115. case CSS::Display::TableHeaderGroup:
  116. case CSS::Display::TableFooterGroup:
  117. return adopt_ref(*new Layout::TableRowGroupBox(document(), *this, move(style)));
  118. case CSS::Display::InlineBlock: {
  119. auto inline_block = adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  120. inline_block->set_inline(true);
  121. return inline_block;
  122. }
  123. case CSS::Display::Flex:
  124. return adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  125. case CSS::Display::TableColumn:
  126. case CSS::Display::TableColumnGroup:
  127. case CSS::Display::TableCaption:
  128. // FIXME: This is just an incorrect placeholder until we improve table layout support.
  129. return adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  130. }
  131. VERIFY_NOT_REACHED();
  132. }
  133. void Element::parse_attribute(const FlyString& name, const String& value)
  134. {
  135. if (name == HTML::AttributeNames::class_) {
  136. auto new_classes = value.split_view(' ');
  137. m_classes.clear();
  138. m_classes.ensure_capacity(new_classes.size());
  139. for (auto& new_class : new_classes) {
  140. m_classes.unchecked_append(new_class);
  141. }
  142. } else if (name == HTML::AttributeNames::style) {
  143. auto parsed_style = parse_css_declaration(CSS::ParsingContext(document()), value);
  144. if (!parsed_style.is_null()) {
  145. m_inline_style = CSS::ElementInlineCSSStyleDeclaration::create_and_take_properties_from(*this, parsed_style.release_nonnull());
  146. set_needs_style_update(true);
  147. }
  148. }
  149. }
  150. enum class StyleDifference {
  151. None,
  152. NeedsRepaint,
  153. NeedsRelayout,
  154. };
  155. static StyleDifference compute_style_difference(const CSS::StyleProperties& old_style, const CSS::StyleProperties& new_style, const Document& document)
  156. {
  157. if (old_style == new_style)
  158. return StyleDifference::None;
  159. bool needs_repaint = false;
  160. bool needs_relayout = false;
  161. if (new_style.display() != old_style.display())
  162. needs_relayout = true;
  163. if (new_style.color_or_fallback(CSS::PropertyID::Color, document, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::Color, document, Color::Black))
  164. needs_repaint = true;
  165. else if (new_style.color_or_fallback(CSS::PropertyID::BackgroundColor, document, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::BackgroundColor, document, Color::Black))
  166. needs_repaint = true;
  167. if (needs_relayout)
  168. return StyleDifference::NeedsRelayout;
  169. if (needs_repaint)
  170. return StyleDifference::NeedsRepaint;
  171. return StyleDifference::None;
  172. }
  173. void Element::recompute_style()
  174. {
  175. set_needs_style_update(false);
  176. VERIFY(parent());
  177. auto old_specified_css_values = m_specified_css_values;
  178. auto new_specified_css_values = document().style_resolver().resolve_style(*this);
  179. m_specified_css_values = new_specified_css_values;
  180. if (!layout_node()) {
  181. if (new_specified_css_values->display() == CSS::Display::None)
  182. return;
  183. // We need a new layout tree here!
  184. Layout::TreeBuilder tree_builder;
  185. tree_builder.build(*this);
  186. return;
  187. }
  188. auto diff = StyleDifference::NeedsRelayout;
  189. if (old_specified_css_values)
  190. diff = compute_style_difference(*old_specified_css_values, *new_specified_css_values, document());
  191. if (diff == StyleDifference::None)
  192. return;
  193. layout_node()->apply_style(*new_specified_css_values);
  194. if (diff == StyleDifference::NeedsRelayout) {
  195. document().schedule_forced_layout();
  196. return;
  197. }
  198. if (diff == StyleDifference::NeedsRepaint) {
  199. layout_node()->set_needs_display();
  200. }
  201. }
  202. NonnullRefPtr<CSS::StyleProperties> Element::computed_style()
  203. {
  204. // FIXME: This implementation is not doing anything it's supposed to.
  205. auto properties = m_specified_css_values->clone();
  206. if (layout_node() && layout_node()->has_style()) {
  207. CSS::PropertyID box_model_metrics[] = {
  208. CSS::PropertyID::MarginTop,
  209. CSS::PropertyID::MarginBottom,
  210. CSS::PropertyID::MarginLeft,
  211. CSS::PropertyID::MarginRight,
  212. CSS::PropertyID::PaddingTop,
  213. CSS::PropertyID::PaddingBottom,
  214. CSS::PropertyID::PaddingLeft,
  215. CSS::PropertyID::PaddingRight,
  216. CSS::PropertyID::BorderTopWidth,
  217. CSS::PropertyID::BorderBottomWidth,
  218. CSS::PropertyID::BorderLeftWidth,
  219. CSS::PropertyID::BorderRightWidth,
  220. };
  221. for (CSS::PropertyID id : box_model_metrics) {
  222. auto prop = m_specified_css_values->property(id);
  223. if (prop.has_value())
  224. properties->set_property(id, prop.value());
  225. }
  226. }
  227. return properties;
  228. }
  229. void Element::set_inner_html(StringView markup)
  230. {
  231. auto new_children = HTML::HTMLDocumentParser::parse_html_fragment(*this, markup);
  232. remove_all_children();
  233. while (!new_children.is_empty()) {
  234. append_child(new_children.take_first());
  235. }
  236. set_needs_style_update(true);
  237. document().invalidate_layout();
  238. }
  239. String Element::inner_html() const
  240. {
  241. auto escape_string = [](const StringView& string, bool attribute_mode) -> String {
  242. // https://html.spec.whatwg.org/multipage/parsing.html#escapingString
  243. StringBuilder builder;
  244. for (auto& ch : string) {
  245. if (ch == '&')
  246. builder.append("&amp;");
  247. // FIXME: also replace U+00A0 NO-BREAK SPACE with &nbsp;
  248. else if (ch == '"' && attribute_mode)
  249. builder.append("&quot;");
  250. else if (ch == '<' && !attribute_mode)
  251. builder.append("&lt;");
  252. else if (ch == '>' && !attribute_mode)
  253. builder.append("&gt;");
  254. else
  255. builder.append(ch);
  256. }
  257. return builder.to_string();
  258. };
  259. StringBuilder builder;
  260. Function<void(const Node&)> recurse = [&](auto& node) {
  261. for (auto* child = node.first_child(); child; child = child->next_sibling()) {
  262. if (child->is_element()) {
  263. auto& element = verify_cast<Element>(*child);
  264. builder.append('<');
  265. builder.append(element.local_name());
  266. element.for_each_attribute([&](auto& name, auto& value) {
  267. builder.append(' ');
  268. builder.append(name);
  269. builder.append('=');
  270. builder.append('"');
  271. builder.append(escape_string(value, true));
  272. builder.append('"');
  273. });
  274. builder.append('>');
  275. recurse(*child);
  276. // FIXME: This should be skipped for void elements
  277. builder.append("</");
  278. builder.append(element.local_name());
  279. builder.append('>');
  280. }
  281. if (child->is_text()) {
  282. auto& text = verify_cast<Text>(*child);
  283. builder.append(escape_string(text.data(), false));
  284. }
  285. // FIXME: Also handle Comment, ProcessingInstruction, DocumentType
  286. }
  287. };
  288. recurse(*this);
  289. return builder.to_string();
  290. }
  291. bool Element::is_focused() const
  292. {
  293. return document().focused_element() == this;
  294. }
  295. bool Element::is_active() const
  296. {
  297. return document().active_element() == this;
  298. }
  299. NonnullRefPtr<HTMLCollection> Element::get_elements_by_tag_name(FlyString const& tag_name)
  300. {
  301. // FIXME: Support "*" for tag_name
  302. // https://dom.spec.whatwg.org/#concept-getelementsbytagname
  303. return HTMLCollection::create(*this, [tag_name](Element const& element) {
  304. if (element.namespace_() == Namespace::HTML)
  305. return element.local_name().to_lowercase() == tag_name.to_lowercase();
  306. return element.local_name() == tag_name;
  307. });
  308. }
  309. NonnullRefPtr<HTMLCollection> Element::get_elements_by_class_name(FlyString const& class_name)
  310. {
  311. return HTMLCollection::create(*this, [class_name, quirks_mode = document().in_quirks_mode()](Element const& element) {
  312. return element.has_class(class_name, quirks_mode ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive);
  313. });
  314. }
  315. void Element::set_shadow_root(RefPtr<ShadowRoot> shadow_root)
  316. {
  317. if (m_shadow_root == shadow_root)
  318. return;
  319. m_shadow_root = move(shadow_root);
  320. invalidate_style();
  321. }
  322. NonnullRefPtr<CSS::CSSStyleDeclaration> Element::style_for_bindings()
  323. {
  324. if (!m_inline_style)
  325. m_inline_style = CSS::ElementInlineCSSStyleDeclaration::create(*this);
  326. return *m_inline_style;
  327. }
  328. // https://dom.spec.whatwg.org/#element-html-uppercased-qualified-name
  329. void Element::make_html_uppercased_qualified_name()
  330. {
  331. // This is allowed by the spec: "User agents could optimize qualified name and HTML-uppercased qualified name by storing them in internal slots."
  332. if (namespace_() == Namespace::HTML /* FIXME: and its node document is an HTML document */)
  333. m_html_uppercased_qualified_name = qualified_name().to_uppercase();
  334. else
  335. m_html_uppercased_qualified_name = qualified_name();
  336. }
  337. // https://html.spec.whatwg.org/multipage/webappapis.html#queue-an-element-task
  338. void Element::queue_an_element_task(HTML::Task::Source source, Function<void()> steps)
  339. {
  340. auto task = HTML::Task::create(source, &document(), [strong_this = NonnullRefPtr(*this), steps = move(steps)] {
  341. steps();
  342. });
  343. HTML::main_thread_event_loop().task_queue().add(move(task));
  344. }
  345. }