HTMLElement.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /*
  2. * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/StringBuilder.h>
  7. #include <LibWeb/ARIA/Roles.h>
  8. #include <LibWeb/Bindings/ExceptionOrUtils.h>
  9. #include <LibWeb/DOM/Document.h>
  10. #include <LibWeb/DOM/IDLEventListener.h>
  11. #include <LibWeb/DOM/ShadowRoot.h>
  12. #include <LibWeb/HTML/BrowsingContext.h>
  13. #include <LibWeb/HTML/DOMStringMap.h>
  14. #include <LibWeb/HTML/EventHandler.h>
  15. #include <LibWeb/HTML/Focus.h>
  16. #include <LibWeb/HTML/HTMLAnchorElement.h>
  17. #include <LibWeb/HTML/HTMLAreaElement.h>
  18. #include <LibWeb/HTML/HTMLBaseElement.h>
  19. #include <LibWeb/HTML/HTMLBodyElement.h>
  20. #include <LibWeb/HTML/HTMLElement.h>
  21. #include <LibWeb/HTML/NavigableContainer.h>
  22. #include <LibWeb/HTML/VisibilityState.h>
  23. #include <LibWeb/HTML/Window.h>
  24. #include <LibWeb/Infra/CharacterTypes.h>
  25. #include <LibWeb/Infra/Strings.h>
  26. #include <LibWeb/Layout/Box.h>
  27. #include <LibWeb/Layout/BreakNode.h>
  28. #include <LibWeb/Layout/TextNode.h>
  29. #include <LibWeb/Painting/PaintableBox.h>
  30. #include <LibWeb/UIEvents/EventNames.h>
  31. #include <LibWeb/UIEvents/FocusEvent.h>
  32. #include <LibWeb/UIEvents/MouseEvent.h>
  33. #include <LibWeb/WebIDL/DOMException.h>
  34. #include <LibWeb/WebIDL/ExceptionOr.h>
  35. namespace Web::HTML {
  36. HTMLElement::HTMLElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  37. : Element(document, move(qualified_name))
  38. {
  39. }
  40. HTMLElement::~HTMLElement() = default;
  41. void HTMLElement::initialize(JS::Realm& realm)
  42. {
  43. Base::initialize(realm);
  44. set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLElementPrototype>(realm, "HTMLElement"));
  45. m_dataset = DOMStringMap::create(*this);
  46. }
  47. void HTMLElement::visit_edges(Cell::Visitor& visitor)
  48. {
  49. Base::visit_edges(visitor);
  50. visitor.visit(m_dataset.ptr());
  51. }
  52. // https://html.spec.whatwg.org/multipage/dom.html#dom-dir
  53. DeprecatedString HTMLElement::dir() const
  54. {
  55. auto dir = attribute(HTML::AttributeNames::dir);
  56. #define __ENUMERATE_HTML_ELEMENT_DIR_ATTRIBUTE(keyword) \
  57. if (dir.equals_ignoring_ascii_case(#keyword##sv)) \
  58. return #keyword##sv;
  59. ENUMERATE_HTML_ELEMENT_DIR_ATTRIBUTES
  60. #undef __ENUMERATE_HTML_ELEMENT_DIR_ATTRIBUTE
  61. return {};
  62. }
  63. void HTMLElement::set_dir(DeprecatedString const& dir)
  64. {
  65. MUST(set_attribute(HTML::AttributeNames::dir, dir));
  66. }
  67. bool HTMLElement::is_editable() const
  68. {
  69. switch (m_content_editable_state) {
  70. case ContentEditableState::True:
  71. return true;
  72. case ContentEditableState::False:
  73. return false;
  74. case ContentEditableState::Inherit:
  75. return parent() && parent()->is_editable();
  76. default:
  77. VERIFY_NOT_REACHED();
  78. }
  79. }
  80. DeprecatedString HTMLElement::content_editable() const
  81. {
  82. switch (m_content_editable_state) {
  83. case ContentEditableState::True:
  84. return "true";
  85. case ContentEditableState::False:
  86. return "false";
  87. case ContentEditableState::Inherit:
  88. return "inherit";
  89. default:
  90. VERIFY_NOT_REACHED();
  91. }
  92. }
  93. // https://html.spec.whatwg.org/multipage/interaction.html#contenteditable
  94. WebIDL::ExceptionOr<void> HTMLElement::set_content_editable(DeprecatedString const& content_editable)
  95. {
  96. if (content_editable.equals_ignoring_ascii_case("inherit"sv)) {
  97. remove_attribute(HTML::AttributeNames::contenteditable);
  98. return {};
  99. }
  100. if (content_editable.equals_ignoring_ascii_case("true"sv)) {
  101. MUST(set_attribute(HTML::AttributeNames::contenteditable, "true"));
  102. return {};
  103. }
  104. if (content_editable.equals_ignoring_ascii_case("false"sv)) {
  105. MUST(set_attribute(HTML::AttributeNames::contenteditable, "false"));
  106. return {};
  107. }
  108. return WebIDL::SyntaxError::create(realm(), "Invalid contentEditable value, must be 'true', 'false', or 'inherit'");
  109. }
  110. void HTMLElement::set_inner_text(StringView text)
  111. {
  112. remove_all_children();
  113. MUST(append_child(document().create_text_node(text)));
  114. set_needs_style_update(true);
  115. }
  116. DeprecatedString HTMLElement::inner_text()
  117. {
  118. StringBuilder builder;
  119. // innerText for element being rendered takes visibility into account, so force a layout and then walk the layout tree.
  120. document().update_layout();
  121. if (!layout_node())
  122. return text_content();
  123. Function<void(Layout::Node const&)> recurse = [&](auto& node) {
  124. for (auto* child = node.first_child(); child; child = child->next_sibling()) {
  125. if (is<Layout::TextNode>(child))
  126. builder.append(verify_cast<Layout::TextNode>(*child).text_for_rendering());
  127. if (is<Layout::BreakNode>(child))
  128. builder.append('\n');
  129. recurse(*child);
  130. }
  131. };
  132. recurse(*layout_node());
  133. return builder.to_deprecated_string();
  134. }
  135. // // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsettop
  136. int HTMLElement::offset_top() const
  137. {
  138. // NOTE: Ensure that layout is up-to-date before looking at metrics.
  139. const_cast<DOM::Document&>(document()).update_layout();
  140. if (is<HTML::HTMLBodyElement>(this) || !layout_node() || !parent_element() || !parent_element()->layout_node())
  141. return 0;
  142. auto position = layout_node()->box_type_agnostic_position();
  143. auto parent_position = parent_element()->layout_node()->box_type_agnostic_position();
  144. return position.y().to_int() - parent_position.y().to_int();
  145. }
  146. // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetleft
  147. int HTMLElement::offset_left() const
  148. {
  149. // NOTE: Ensure that layout is up-to-date before looking at metrics.
  150. const_cast<DOM::Document&>(document()).update_layout();
  151. if (is<HTML::HTMLBodyElement>(this) || !layout_node() || !parent_element() || !parent_element()->layout_node())
  152. return 0;
  153. auto position = layout_node()->box_type_agnostic_position();
  154. auto parent_position = parent_element()->layout_node()->box_type_agnostic_position();
  155. return position.x().to_int() - parent_position.x().to_int();
  156. }
  157. // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetwidth
  158. int HTMLElement::offset_width() const
  159. {
  160. // NOTE: Ensure that layout is up-to-date before looking at metrics.
  161. const_cast<DOM::Document&>(document()).update_layout();
  162. // 1. If the element does not have any associated CSS layout box return zero and terminate this algorithm.
  163. if (!paintable_box())
  164. return 0;
  165. // 2. Return the width of the axis-aligned bounding box of the border boxes of all fragments generated by the element’s principal box,
  166. // ignoring any transforms that apply to the element and its ancestors.
  167. // FIXME: Account for inline boxes.
  168. return paintable_box()->border_box_width().to_int();
  169. }
  170. // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetheight
  171. int HTMLElement::offset_height() const
  172. {
  173. // NOTE: Ensure that layout is up-to-date before looking at metrics.
  174. const_cast<DOM::Document&>(document()).update_layout();
  175. // 1. If the element does not have any associated CSS layout box return zero and terminate this algorithm.
  176. if (!paintable_box())
  177. return 0;
  178. // 2. Return the height of the axis-aligned bounding box of the border boxes of all fragments generated by the element’s principal box,
  179. // ignoring any transforms that apply to the element and its ancestors.
  180. // FIXME: Account for inline boxes.
  181. return paintable_box()->border_box_height().to_int();
  182. }
  183. // https://html.spec.whatwg.org/multipage/links.html#cannot-navigate
  184. bool HTMLElement::cannot_navigate() const
  185. {
  186. // An element element cannot navigate if one of the following is true:
  187. // - element's node document is not fully active
  188. if (!document().is_fully_active())
  189. return true;
  190. // - element is not an a element and is not connected.
  191. return !is<HTML::HTMLAnchorElement>(this) && !is_connected();
  192. }
  193. void HTMLElement::attribute_changed(DeprecatedFlyString const& name, DeprecatedString const& value)
  194. {
  195. Element::attribute_changed(name, value);
  196. if (name == HTML::AttributeNames::contenteditable) {
  197. if (value.is_null()) {
  198. m_content_editable_state = ContentEditableState::Inherit;
  199. } else {
  200. if (value.is_empty() || value.equals_ignoring_ascii_case("true"sv)) {
  201. // "true", an empty string or a missing value map to the "true" state.
  202. m_content_editable_state = ContentEditableState::True;
  203. } else if (value.equals_ignoring_ascii_case("false"sv)) {
  204. // "false" maps to the "false" state.
  205. m_content_editable_state = ContentEditableState::False;
  206. } else {
  207. // Having no such attribute or an invalid value maps to the "inherit" state.
  208. m_content_editable_state = ContentEditableState::Inherit;
  209. }
  210. }
  211. }
  212. // 1. If namespace is not null, or localName is not the name of an event handler content attribute on element, then return.
  213. // FIXME: Add the namespace part once we support attribute namespaces.
  214. #undef __ENUMERATE
  215. #define __ENUMERATE(attribute_name, event_name) \
  216. if (name == HTML::AttributeNames::attribute_name) { \
  217. element_event_handler_attribute_changed(event_name, String::from_deprecated_string(value).release_value()); \
  218. }
  219. ENUMERATE_GLOBAL_EVENT_HANDLERS(__ENUMERATE)
  220. #undef __ENUMERATE
  221. }
  222. // https://html.spec.whatwg.org/multipage/interaction.html#dom-focus
  223. void HTMLElement::focus()
  224. {
  225. // 1. If the element is marked as locked for focus, then return.
  226. if (m_locked_for_focus)
  227. return;
  228. // 2. Mark the element as locked for focus.
  229. m_locked_for_focus = true;
  230. // 3. Run the focusing steps for the element.
  231. run_focusing_steps(this);
  232. // FIXME: 4. If the value of the preventScroll dictionary member of options is false,
  233. // then scroll the element into view with scroll behavior "auto",
  234. // block flow direction position set to an implementation-defined value,
  235. // and inline base direction position set to an implementation-defined value.
  236. // 5. Unmark the element as locked for focus.
  237. m_locked_for_focus = false;
  238. }
  239. // https://html.spec.whatwg.org/multipage/webappapis.html#fire-a-synthetic-pointer-event
  240. bool HTMLElement::fire_a_synthetic_pointer_event(FlyString const& type, DOM::Element& target, bool not_trusted)
  241. {
  242. // 1. Let event be the result of creating an event using PointerEvent.
  243. // 2. Initialize event's type attribute to e.
  244. // FIXME: Actually create a PointerEvent!
  245. auto event = UIEvents::MouseEvent::create(realm(), type);
  246. // 3. Initialize event's bubbles and cancelable attributes to true.
  247. event->set_bubbles(true);
  248. event->set_cancelable(true);
  249. // 4. Set event's composed flag.
  250. event->set_composed(true);
  251. // 5. If the not trusted flag is set, initialize event's isTrusted attribute to false.
  252. if (not_trusted) {
  253. event->set_is_trusted(false);
  254. }
  255. // FIXME: 6. Initialize event's ctrlKey, shiftKey, altKey, and metaKey attributes according to the current state
  256. // of the key input device, if any (false for any keys that are not available).
  257. // FIXME: 7. Initialize event's view attribute to target's node document's Window object, if any, and null otherwise.
  258. // FIXME: 8. event's getModifierState() method is to return values appropriately describing the current state of the key input device.
  259. // 9. Return the result of dispatching event at target.
  260. return target.dispatch_event(event);
  261. }
  262. // https://html.spec.whatwg.org/multipage/interaction.html#dom-click
  263. void HTMLElement::click()
  264. {
  265. // FIXME: 1. If this element is a form control that is disabled, then return.
  266. // 2. If this element's click in progress flag is set, then return.
  267. if (m_click_in_progress)
  268. return;
  269. // 3. Set this element's click in progress flag.
  270. m_click_in_progress = true;
  271. // FIXME: 4. Fire a synthetic pointer event named click at this element, with the not trusted flag set.
  272. fire_a_synthetic_pointer_event(HTML::EventNames::click, *this, true);
  273. // 5. Unset this element's click in progress flag.
  274. m_click_in_progress = false;
  275. }
  276. // https://html.spec.whatwg.org/multipage/interaction.html#dom-blur
  277. void HTMLElement::blur()
  278. {
  279. // The blur() method, when invoked, should run the unfocusing steps for the element on which the method was called.
  280. run_unfocusing_steps(this);
  281. // User agents may selectively or uniformly ignore calls to this method for usability reasons.
  282. }
  283. Optional<ARIA::Role> HTMLElement::default_role() const
  284. {
  285. // https://www.w3.org/TR/html-aria/#el-article
  286. if (local_name() == TagNames::article)
  287. return ARIA::Role::article;
  288. // https://www.w3.org/TR/html-aria/#el-aside
  289. if (local_name() == TagNames::aside)
  290. return ARIA::Role::complementary;
  291. // https://www.w3.org/TR/html-aria/#el-b
  292. if (local_name() == TagNames::b)
  293. return ARIA::Role::generic;
  294. // https://www.w3.org/TR/html-aria/#el-bdi
  295. if (local_name() == TagNames::bdi)
  296. return ARIA::Role::generic;
  297. // https://www.w3.org/TR/html-aria/#el-bdo
  298. if (local_name() == TagNames::bdo)
  299. return ARIA::Role::generic;
  300. // https://www.w3.org/TR/html-aria/#el-code
  301. if (local_name() == TagNames::code)
  302. return ARIA::Role::code;
  303. // https://www.w3.org/TR/html-aria/#el-dfn
  304. if (local_name() == TagNames::dfn)
  305. return ARIA::Role::term;
  306. // https://www.w3.org/TR/html-aria/#el-em
  307. if (local_name() == TagNames::em)
  308. return ARIA::Role::emphasis;
  309. // https://www.w3.org/TR/html-aria/#el-figure
  310. if (local_name() == TagNames::figure)
  311. return ARIA::Role::figure;
  312. // https://www.w3.org/TR/html-aria/#el-footer
  313. if (local_name() == TagNames::footer) {
  314. // TODO: If not a descendant of an article, aside, main, nav or section element, or an element with role=article, complementary, main, navigation or region then role=contentinfo
  315. // Otherwise, role=generic
  316. return ARIA::Role::generic;
  317. }
  318. // https://www.w3.org/TR/html-aria/#el-header
  319. if (local_name() == TagNames::header) {
  320. // TODO: If not a descendant of an article, aside, main, nav or section element, or an element with role=article, complementary, main, navigation or region then role=banner
  321. // Otherwise, role=generic
  322. return ARIA::Role::generic;
  323. }
  324. // https://www.w3.org/TR/html-aria/#el-hgroup
  325. if (local_name() == TagNames::hgroup)
  326. return ARIA::Role::generic;
  327. // https://www.w3.org/TR/html-aria/#el-i
  328. if (local_name() == TagNames::i)
  329. return ARIA::Role::generic;
  330. // https://www.w3.org/TR/html-aria/#el-main
  331. if (local_name() == TagNames::main)
  332. return ARIA::Role::main;
  333. // https://www.w3.org/TR/html-aria/#el-nav
  334. if (local_name() == TagNames::nav)
  335. return ARIA::Role::navigation;
  336. // https://www.w3.org/TR/html-aria/#el-samp
  337. if (local_name() == TagNames::samp)
  338. return ARIA::Role::generic;
  339. // https://www.w3.org/TR/html-aria/#el-section
  340. if (local_name() == TagNames::section) {
  341. // TODO: role=region if the section element has an accessible name
  342. // Otherwise, no corresponding role
  343. return ARIA::Role::region;
  344. }
  345. // https://www.w3.org/TR/html-aria/#el-small
  346. if (local_name() == TagNames::small)
  347. return ARIA::Role::generic;
  348. // https://www.w3.org/TR/html-aria/#el-strong
  349. if (local_name() == TagNames::strong)
  350. return ARIA::Role::strong;
  351. // https://www.w3.org/TR/html-aria/#el-sub
  352. if (local_name() == TagNames::sub)
  353. return ARIA::Role::subscript;
  354. // https://www.w3.org/TR/html-aria/#el-summary
  355. if (local_name() == TagNames::summary)
  356. return ARIA::Role::button;
  357. // https://www.w3.org/TR/html-aria/#el-sup
  358. if (local_name() == TagNames::sup)
  359. return ARIA::Role::superscript;
  360. // https://www.w3.org/TR/html-aria/#el-u
  361. if (local_name() == TagNames::u)
  362. return ARIA::Role::generic;
  363. return {};
  364. }
  365. // https://html.spec.whatwg.org/multipage/semantics.html#get-an-element's-target
  366. DeprecatedString HTMLElement::get_an_elements_target() const
  367. {
  368. // To get an element's target, given an a, area, or form element element, run these steps:
  369. // 1. If element has a target attribute, then return that attribute's value.
  370. if (has_attribute(AttributeNames::target))
  371. return attribute(AttributeNames::target);
  372. // FIXME: 2. If element's node document contains a base element with a
  373. // target attribute, then return the value of the target attribute of the
  374. // first such base element.
  375. // 3. Return the empty string.
  376. return DeprecatedString::empty();
  377. }
  378. // https://html.spec.whatwg.org/multipage/links.html#get-an-element's-noopener
  379. TokenizedFeature::NoOpener HTMLElement::get_an_elements_noopener(StringView target) const
  380. {
  381. // To get an element's noopener, given an a, area, or form element element and a string target:
  382. auto rel = attribute(HTML::AttributeNames::rel).to_lowercase();
  383. auto link_types = rel.view().split_view_if(Infra::is_ascii_whitespace);
  384. // 1. If element's link types include the noopener or noreferrer keyword, then return true.
  385. if (link_types.contains_slow("noopener"sv) || link_types.contains_slow("noreferrer"sv))
  386. return TokenizedFeature::NoOpener::Yes;
  387. // 2. If element's link types do not include the opener keyword and
  388. // target is an ASCII case-insensitive match for "_blank", then return true.
  389. if (!link_types.contains_slow("opener"sv) && Infra::is_ascii_case_insensitive_match(target, "_blank"sv))
  390. return TokenizedFeature::NoOpener::Yes;
  391. // 3. Return false.
  392. return TokenizedFeature::NoOpener::No;
  393. }
  394. }