Element.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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/ResolvedCSSStyleDeclaration.h>
  11. #include <LibWeb/CSS/StyleInvalidator.h>
  12. #include <LibWeb/DOM/DOMException.h>
  13. #include <LibWeb/DOM/Document.h>
  14. #include <LibWeb/DOM/Element.h>
  15. #include <LibWeb/DOM/ExceptionOr.h>
  16. #include <LibWeb/DOM/HTMLCollection.h>
  17. #include <LibWeb/DOM/ShadowRoot.h>
  18. #include <LibWeb/DOM/Text.h>
  19. #include <LibWeb/DOMParsing/InnerHTML.h>
  20. #include <LibWeb/Geometry/DOMRect.h>
  21. #include <LibWeb/HTML/EventLoop/EventLoop.h>
  22. #include <LibWeb/HTML/Parser/HTMLParser.h>
  23. #include <LibWeb/Layout/BlockBox.h>
  24. #include <LibWeb/Layout/InlineNode.h>
  25. #include <LibWeb/Layout/ListItemBox.h>
  26. #include <LibWeb/Layout/TableBox.h>
  27. #include <LibWeb/Layout/TableCellBox.h>
  28. #include <LibWeb/Layout/TableRowBox.h>
  29. #include <LibWeb/Layout/TableRowGroupBox.h>
  30. #include <LibWeb/Layout/TreeBuilder.h>
  31. #include <LibWeb/Namespace.h>
  32. #include <LibWeb/Page/BrowsingContext.h>
  33. namespace Web::DOM {
  34. Element::Element(Document& document, QualifiedName qualified_name)
  35. : ParentNode(document, NodeType::ELEMENT_NODE)
  36. , m_qualified_name(move(qualified_name))
  37. {
  38. make_html_uppercased_qualified_name();
  39. }
  40. Element::~Element()
  41. {
  42. }
  43. Attribute* Element::find_attribute(const FlyString& name)
  44. {
  45. for (auto& attribute : m_attributes) {
  46. if (attribute.name() == name)
  47. return &attribute;
  48. }
  49. return nullptr;
  50. }
  51. const Attribute* Element::find_attribute(const FlyString& name) const
  52. {
  53. for (auto& attribute : m_attributes) {
  54. if (attribute.name() == name)
  55. return &attribute;
  56. }
  57. return nullptr;
  58. }
  59. String Element::attribute(const FlyString& name) const
  60. {
  61. if (auto* attribute = find_attribute(name))
  62. return attribute->value();
  63. return {};
  64. }
  65. ExceptionOr<void> Element::set_attribute(const FlyString& name, const String& value)
  66. {
  67. // FIXME: Proper name validation
  68. if (name.is_empty())
  69. return InvalidCharacterError::create("Attribute name must not be empty");
  70. CSS::StyleInvalidator style_invalidator(document());
  71. if (auto* attribute = find_attribute(name))
  72. attribute->set_value(value);
  73. else
  74. m_attributes.empend(name, value);
  75. parse_attribute(name, value);
  76. return {};
  77. }
  78. void Element::remove_attribute(const FlyString& name)
  79. {
  80. CSS::StyleInvalidator style_invalidator(document());
  81. m_attributes.remove_first_matching([&](auto& attribute) { return attribute.name() == name; });
  82. }
  83. bool Element::has_class(const FlyString& class_name, CaseSensitivity case_sensitivity) const
  84. {
  85. return any_of(m_classes, [&](auto& it) {
  86. return case_sensitivity == CaseSensitivity::CaseSensitive
  87. ? it == class_name
  88. : it.to_lowercase() == class_name.to_lowercase();
  89. });
  90. }
  91. RefPtr<Layout::Node> Element::create_layout_node()
  92. {
  93. auto style = document().style_computer().compute_style(*this);
  94. const_cast<Element&>(*this).m_specified_css_values = style;
  95. auto display = style->display();
  96. if (display == CSS::Display::None)
  97. return nullptr;
  98. if (local_name() == "noscript" && document().is_scripting_enabled())
  99. return nullptr;
  100. switch (display) {
  101. case CSS::Display::None:
  102. VERIFY_NOT_REACHED();
  103. break;
  104. case CSS::Display::Block:
  105. return adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  106. case CSS::Display::Inline:
  107. if (style->float_().value_or(CSS::Float::None) != CSS::Float::None)
  108. return adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  109. return adopt_ref(*new Layout::InlineNode(document(), *this, move(style)));
  110. case CSS::Display::ListItem:
  111. return adopt_ref(*new Layout::ListItemBox(document(), *this, move(style)));
  112. case CSS::Display::Table:
  113. return adopt_ref(*new Layout::TableBox(document(), this, move(style)));
  114. case CSS::Display::TableRow:
  115. return adopt_ref(*new Layout::TableRowBox(document(), this, move(style)));
  116. case CSS::Display::TableCell:
  117. return adopt_ref(*new Layout::TableCellBox(document(), this, move(style)));
  118. case CSS::Display::TableRowGroup:
  119. case CSS::Display::TableHeaderGroup:
  120. case CSS::Display::TableFooterGroup:
  121. return adopt_ref(*new Layout::TableRowGroupBox(document(), *this, move(style)));
  122. case CSS::Display::InlineBlock: {
  123. auto inline_block = adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  124. inline_block->set_inline(true);
  125. return inline_block;
  126. }
  127. case CSS::Display::Flex:
  128. return adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  129. case CSS::Display::TableColumn:
  130. case CSS::Display::TableColumnGroup:
  131. case CSS::Display::TableCaption:
  132. // FIXME: This is just an incorrect placeholder until we improve table layout support.
  133. return adopt_ref(*new Layout::BlockBox(document(), this, move(style)));
  134. }
  135. VERIFY_NOT_REACHED();
  136. }
  137. void Element::parse_attribute(const FlyString& name, const String& value)
  138. {
  139. if (name == HTML::AttributeNames::class_) {
  140. auto new_classes = value.split_view(' ');
  141. m_classes.clear();
  142. m_classes.ensure_capacity(new_classes.size());
  143. for (auto& new_class : new_classes) {
  144. m_classes.unchecked_append(new_class);
  145. }
  146. } else if (name == HTML::AttributeNames::style) {
  147. auto parsed_style = parse_css_declaration(CSS::ParsingContext(document()), value);
  148. if (!parsed_style.is_null()) {
  149. m_inline_style = CSS::ElementInlineCSSStyleDeclaration::create_and_take_properties_from(*this, parsed_style.release_nonnull());
  150. set_needs_style_update(true);
  151. }
  152. }
  153. }
  154. enum class StyleDifference {
  155. None,
  156. NeedsRepaint,
  157. NeedsRelayout,
  158. };
  159. static StyleDifference compute_style_difference(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style, Layout::NodeWithStyle const& node)
  160. {
  161. if (old_style == new_style)
  162. return StyleDifference::None;
  163. bool needs_repaint = false;
  164. bool needs_relayout = false;
  165. if (new_style.display() != old_style.display())
  166. needs_relayout = true;
  167. if (new_style.color_or_fallback(CSS::PropertyID::Color, node, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::Color, node, Color::Black))
  168. needs_repaint = true;
  169. else if (new_style.color_or_fallback(CSS::PropertyID::BackgroundColor, node, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::BackgroundColor, node, Color::Black))
  170. needs_repaint = true;
  171. if (needs_relayout)
  172. return StyleDifference::NeedsRelayout;
  173. if (needs_repaint)
  174. return StyleDifference::NeedsRepaint;
  175. return StyleDifference::None;
  176. }
  177. void Element::recompute_style()
  178. {
  179. set_needs_style_update(false);
  180. VERIFY(parent());
  181. auto old_specified_css_values = m_specified_css_values;
  182. auto new_specified_css_values = document().style_computer().compute_style(*this);
  183. m_specified_css_values = new_specified_css_values;
  184. if (!layout_node()) {
  185. if (new_specified_css_values->display() == CSS::Display::None)
  186. return;
  187. // We need a new layout tree here!
  188. Layout::TreeBuilder tree_builder;
  189. tree_builder.build(*this);
  190. return;
  191. }
  192. auto diff = StyleDifference::NeedsRelayout;
  193. if (old_specified_css_values && layout_node())
  194. diff = compute_style_difference(*old_specified_css_values, *new_specified_css_values, *layout_node());
  195. if (diff == StyleDifference::None)
  196. return;
  197. layout_node()->apply_style(*new_specified_css_values);
  198. if (diff == StyleDifference::NeedsRelayout) {
  199. document().schedule_forced_layout();
  200. return;
  201. }
  202. if (diff == StyleDifference::NeedsRepaint) {
  203. layout_node()->set_needs_display();
  204. }
  205. }
  206. NonnullRefPtr<CSS::StyleProperties> Element::computed_style()
  207. {
  208. auto element_computed_style = CSS::ResolvedCSSStyleDeclaration::create(*this);
  209. auto properties = CSS::StyleProperties::create();
  210. for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) {
  211. auto property_id = (CSS::PropertyID)i;
  212. auto maybe_value = element_computed_style->property(property_id);
  213. if (!maybe_value.has_value())
  214. continue;
  215. properties->set_property(property_id, maybe_value.release_value().value);
  216. }
  217. return properties;
  218. }
  219. ExceptionOr<void> Element::set_inner_html(String const& markup)
  220. {
  221. auto result = DOMParsing::InnerHTML::inner_html_setter(*this, markup);
  222. if (result.is_exception())
  223. return result.exception();
  224. set_needs_style_update(true);
  225. document().invalidate_layout();
  226. return {};
  227. }
  228. // https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml
  229. String Element::inner_html() const
  230. {
  231. return serialize_fragment(/* FIXME: Providing true for the require well-formed flag (which may throw) */);
  232. }
  233. bool Element::is_focused() const
  234. {
  235. return document().focused_element() == this;
  236. }
  237. bool Element::is_active() const
  238. {
  239. return document().active_element() == this;
  240. }
  241. NonnullRefPtr<HTMLCollection> Element::get_elements_by_class_name(FlyString const& class_name)
  242. {
  243. return HTMLCollection::create(*this, [class_name, quirks_mode = document().in_quirks_mode()](Element const& element) {
  244. return element.has_class(class_name, quirks_mode ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive);
  245. });
  246. }
  247. void Element::set_shadow_root(RefPtr<ShadowRoot> shadow_root)
  248. {
  249. if (m_shadow_root == shadow_root)
  250. return;
  251. m_shadow_root = move(shadow_root);
  252. invalidate_style();
  253. }
  254. NonnullRefPtr<CSS::CSSStyleDeclaration> Element::style_for_bindings()
  255. {
  256. if (!m_inline_style)
  257. m_inline_style = CSS::ElementInlineCSSStyleDeclaration::create(*this);
  258. return *m_inline_style;
  259. }
  260. // https://dom.spec.whatwg.org/#element-html-uppercased-qualified-name
  261. void Element::make_html_uppercased_qualified_name()
  262. {
  263. // This is allowed by the spec: "User agents could optimize qualified name and HTML-uppercased qualified name by storing them in internal slots."
  264. if (namespace_() == Namespace::HTML /* FIXME: and its node document is an HTML document */)
  265. m_html_uppercased_qualified_name = qualified_name().to_uppercase();
  266. else
  267. m_html_uppercased_qualified_name = qualified_name();
  268. }
  269. // https://html.spec.whatwg.org/multipage/webappapis.html#queue-an-element-task
  270. void Element::queue_an_element_task(HTML::Task::Source source, Function<void()> steps)
  271. {
  272. auto task = HTML::Task::create(source, &document(), [strong_this = NonnullRefPtr(*this), steps = move(steps)] {
  273. steps();
  274. });
  275. HTML::main_thread_event_loop().task_queue().add(move(task));
  276. }
  277. // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
  278. bool Element::is_void_element() const
  279. {
  280. return local_name().is_one_of(HTML::TagNames::area, HTML::TagNames::base, HTML::TagNames::br, HTML::TagNames::col, HTML::TagNames::embed, HTML::TagNames::hr, HTML::TagNames::img, HTML::TagNames::input, HTML::TagNames::link, HTML::TagNames::meta, HTML::TagNames::param, HTML::TagNames::source, HTML::TagNames::track, HTML::TagNames::wbr);
  281. }
  282. // https://html.spec.whatwg.org/multipage/parsing.html#serializes-as-void
  283. bool Element::serializes_as_void() const
  284. {
  285. return is_void_element() || local_name().is_one_of(HTML::TagNames::basefont, HTML::TagNames::bgsound, HTML::TagNames::frame, HTML::TagNames::keygen);
  286. }
  287. // https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect
  288. NonnullRefPtr<Geometry::DOMRect> Element::get_bounding_client_rect() const
  289. {
  290. // FIXME: Support inline layout nodes as well.
  291. if (!layout_node() || !layout_node()->is_box())
  292. return Geometry::DOMRect::create(0, 0, 0, 0);
  293. VERIFY(document().browsing_context());
  294. auto viewport_offset = document().browsing_context()->viewport_scroll_offset();
  295. auto& box = static_cast<Layout::Box const&>(*layout_node());
  296. return Geometry::DOMRect::create(box.absolute_rect().translated(-viewport_offset.x(), -viewport_offset.y()));
  297. }
  298. }