Algorithms.cpp 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875
  1. /*
  2. * Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/DOM/CharacterData.h>
  7. #include <LibWeb/DOM/DocumentFragment.h>
  8. #include <LibWeb/DOM/DocumentType.h>
  9. #include <LibWeb/DOM/Element.h>
  10. #include <LibWeb/DOM/ElementFactory.h>
  11. #include <LibWeb/DOM/Range.h>
  12. #include <LibWeb/DOM/Text.h>
  13. #include <LibWeb/Editing/CommandNames.h>
  14. #include <LibWeb/Editing/Internal/Algorithms.h>
  15. #include <LibWeb/HTML/HTMLBRElement.h>
  16. #include <LibWeb/HTML/HTMLElement.h>
  17. #include <LibWeb/HTML/HTMLImageElement.h>
  18. #include <LibWeb/HTML/HTMLOListElement.h>
  19. #include <LibWeb/HTML/HTMLUListElement.h>
  20. #include <LibWeb/Infra/CharacterTypes.h>
  21. #include <LibWeb/Layout/Node.h>
  22. #include <LibWeb/Namespace.h>
  23. namespace Web::Editing {
  24. // https://w3c.github.io/editing/docs/execCommand/#block-extend
  25. GC::Ref<DOM::Range> block_extend_a_range(DOM::Range& range)
  26. {
  27. // 1. Let start node, start offset, end node, and end offset be the start and end nodes and offsets of range.
  28. GC::Ptr<DOM::Node> start_node = range.start_container();
  29. auto start_offset = range.start_offset();
  30. GC::Ptr<DOM::Node> end_node = range.end_container();
  31. auto end_offset = range.end_offset();
  32. // 2. If some inclusive ancestor of start node is an li, set start offset to the index of the last such li in tree
  33. // order, and set start node to that li's parent.
  34. auto ancestor = start_node;
  35. do {
  36. if (is<HTML::HTMLLIElement>(*ancestor)) {
  37. start_offset = ancestor->index();
  38. start_node = ancestor->parent();
  39. break;
  40. }
  41. ancestor = ancestor->parent();
  42. } while (ancestor);
  43. // 3. If (start node, start offset) is not a block start point, repeat the following steps:
  44. if (!is_block_start_point(*start_node, start_offset)) {
  45. do {
  46. // 1. If start offset is zero, set it to start node's index, then set start node to its parent.
  47. if (start_offset == 0) {
  48. start_offset = start_node->index();
  49. start_node = start_node->parent();
  50. }
  51. // 2. Otherwise, subtract one from start offset.
  52. else {
  53. --start_offset;
  54. }
  55. // 3. If (start node, start offset) is a block boundary point, break from this loop.
  56. } while (!is_block_boundary_point(*start_node, start_offset));
  57. }
  58. // 4. While start offset is zero and start node's parent is not null, set start offset to start node's index, then
  59. // set start node to its parent.
  60. while (start_offset == 0 && start_node->parent()) {
  61. start_offset = start_node->index();
  62. start_node = start_node->parent();
  63. }
  64. // 5. If some inclusive ancestor of end node is an li, set end offset to one plus the index of the last such li in
  65. // tree order, and set end node to that li's parent.
  66. ancestor = end_node;
  67. do {
  68. if (is<HTML::HTMLLIElement>(*ancestor)) {
  69. end_offset = ancestor->index() + 1;
  70. end_node = ancestor->parent();
  71. break;
  72. }
  73. ancestor = ancestor->parent();
  74. } while (ancestor);
  75. // 6. If (end node, end offset) is not a block end point, repeat the following steps:
  76. if (!is_block_end_point(*end_node, end_offset)) {
  77. do {
  78. // 1. If end offset is end node's length, set it to one plus end node's index, then set end node to its
  79. // parent.
  80. if (end_offset == end_node->length()) {
  81. end_offset = end_node->index() + 1;
  82. end_node = end_node->parent();
  83. }
  84. // 2. Otherwise, add one to end offset.
  85. else {
  86. ++end_offset;
  87. }
  88. // 3. If (end node, end offset) is a block boundary point, break from this loop.
  89. } while (!is_block_boundary_point(*end_node, end_offset));
  90. }
  91. // 7. While end offset is end node's length and end node's parent is not null, set end offset to one plus end node's
  92. // index, then set end node to its parent.
  93. while (end_offset == end_node->length() && end_node->parent()) {
  94. end_offset = end_node->index() + 1;
  95. end_node = end_node->parent();
  96. }
  97. // 8. Let new range be a new range whose start and end nodes and offsets are start node, start offset, end node, and
  98. // end offset.
  99. auto new_range = DOM::Range::create(*start_node, start_offset, *end_node, end_offset);
  100. // 9. Return new range.
  101. return new_range;
  102. }
  103. // https://w3c.github.io/editing/docs/execCommand/#canonical-space-sequence
  104. String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_breaking_end)
  105. {
  106. auto n = length;
  107. // 1. If n is zero, return the empty string.
  108. if (n == 0)
  109. return {};
  110. // 2. If n is one and both non-breaking start and non-breaking end are false, return a single
  111. // space (U+0020).
  112. if (n == 1 && !non_breaking_start && !non_breaking_end)
  113. return " "_string;
  114. // 3. If n is one, return a single non-breaking space (U+00A0).
  115. if (n == 1)
  116. return "\u00A0"_string;
  117. // 4. Let buffer be the empty string.
  118. StringBuilder buffer;
  119. // 5. If non-breaking start is true, let repeated pair be U+00A0 U+0020. Otherwise, let it be
  120. // U+0020 U+00A0.
  121. auto repeated_pair = non_breaking_start ? "\u00A0 "sv : " \u00A0"sv;
  122. // 6. While n is greater than three, append repeated pair to buffer and subtract two from n.
  123. while (n > 3) {
  124. buffer.append(repeated_pair);
  125. n -= 2;
  126. }
  127. // 7. If n is three, append a three-code unit string to buffer depending on non-breaking start
  128. // and non-breaking end:
  129. if (n == 3) {
  130. // non-breaking start and non-breaking end false
  131. // U+0020 U+00A0 U+0020
  132. if (!non_breaking_start && !non_breaking_end)
  133. buffer.append(" \u00A0 "sv);
  134. // non-breaking start true, non-breaking end false
  135. // U+00A0 U+00A0 U+0020
  136. else if (non_breaking_start && !non_breaking_end)
  137. buffer.append("\u00A0\u00A0 "sv);
  138. // non-breaking start false, non-breaking end true
  139. // U+0020 U+00A0 U+00A0
  140. else if (!non_breaking_start)
  141. buffer.append(" \u00A0\u00A0"sv);
  142. // non-breaking start and non-breaking end both true
  143. // U+00A0 U+0020 U+00A0
  144. else
  145. buffer.append("\u00A0 \u00A0"sv);
  146. }
  147. // 8. Otherwise, append a two-code unit string to buffer depending on non-breaking start and
  148. // non-breaking end:
  149. else {
  150. // non-breaking start and non-breaking end false
  151. // non-breaking start true, non-breaking end false
  152. // U+00A0 U+0020
  153. if (!non_breaking_start && !non_breaking_end)
  154. buffer.append("\u00A0 "sv);
  155. // non-breaking start false, non-breaking end true
  156. // U+0020 U+00A0
  157. else if (!non_breaking_start)
  158. buffer.append(" \u00A0"sv);
  159. // non-breaking start and non-breaking end both true
  160. // U+00A0 U+00A0
  161. else
  162. buffer.append("\u00A0\u00A0"sv);
  163. }
  164. // 9. Return buffer.
  165. return MUST(buffer.to_string());
  166. }
  167. // https://w3c.github.io/editing/docs/execCommand/#canonicalize-whitespace
  168. void canonicalize_whitespace(GC::Ref<DOM::Node> node, u32 offset, bool fix_collapsed_space)
  169. {
  170. // 1. If node is neither editable nor an editing host, abort these steps.
  171. if (!node->is_editable() || !is_editing_host(node))
  172. return;
  173. // 2. Let start node equal node and let start offset equal offset.
  174. auto start_node = node;
  175. auto start_offset = offset;
  176. // 3. Repeat the following steps:
  177. while (true) {
  178. // 1. If start node has a child in the same editing host with index start offset minus one,
  179. // set start node to that child, then set start offset to start node's length.
  180. auto* offset_minus_one_child = start_node->child_at_index(start_offset - 1);
  181. if (offset_minus_one_child && is_in_same_editing_host(*start_node, *offset_minus_one_child)) {
  182. start_node = *offset_minus_one_child;
  183. start_offset = start_node->length();
  184. continue;
  185. }
  186. // 2. Otherwise, if start offset is zero and start node does not follow a line break and
  187. // start node's parent is in the same editing host, set start offset to start node's
  188. // index, then set start node to its parent.
  189. if (start_offset == 0 && !follows_a_line_break(start_node) && is_in_same_editing_host(*start_node, *start_node->parent())) {
  190. start_offset = start_node->index();
  191. start_node = *start_node->parent();
  192. continue;
  193. }
  194. // 3. Otherwise, if start node is a Text node and its parent's resolved value for
  195. // "white-space" is neither "pre" nor "pre-wrap" and start offset is not zero and the
  196. // (start offset − 1)st code unit of start node's data is a space (0x0020) or
  197. // non-breaking space (0x00A0), subtract one from start offset.
  198. auto* layout_node = start_node->parent()->layout_node();
  199. if (layout_node && is<DOM::Text>(*start_node) && start_offset != 0) {
  200. auto parent_white_space = layout_node->computed_values().white_space();
  201. // FIXME: Find a way to get code points directly from the UTF-8 string
  202. auto start_node_data = *start_node->text_content();
  203. auto utf16_code_units = MUST(AK::utf8_to_utf16(start_node_data));
  204. auto offset_minus_one_code_point = Utf16View { utf16_code_units }.code_point_at(start_offset - 1);
  205. if (parent_white_space != CSS::WhiteSpace::Pre && parent_white_space != CSS::WhiteSpace::PreWrap
  206. && (offset_minus_one_code_point == 0x20 || offset_minus_one_code_point == 0xA0)) {
  207. --start_offset;
  208. continue;
  209. }
  210. }
  211. // 4. Otherwise, break from this loop.
  212. break;
  213. }
  214. // 4. Let end node equal start node and end offset equal start offset.
  215. auto end_node = start_node;
  216. auto end_offset = start_offset;
  217. // 5. Let length equal zero.
  218. auto length = 0;
  219. // 6. Let collapse spaces be true if start offset is zero and start node follows a line break,
  220. // otherwise false.
  221. auto collapse_spaces = start_offset == 0 && follows_a_line_break(start_node);
  222. // 7. Repeat the following steps:
  223. while (true) {
  224. // 1. If end node has a child in the same editing host with index end offset, set end node
  225. // to that child, then set end offset to zero.
  226. auto* offset_child = end_node->child_at_index(end_offset);
  227. if (offset_child && is_in_same_editing_host(*end_node, *offset_child)) {
  228. end_node = *offset_child;
  229. end_offset = 0;
  230. continue;
  231. }
  232. // 2. Otherwise, if end offset is end node's length and end node does not precede a line
  233. // break and end node's parent is in the same editing host, set end offset to one plus
  234. // end node's index, then set end node to its parent.
  235. if (end_offset == end_node->length() && !precedes_a_line_break(end_node) && is_in_same_editing_host(*end_node, *end_node->parent())) {
  236. end_offset = end_node->index() + 1;
  237. end_node = *end_node->parent();
  238. continue;
  239. }
  240. // 3. Otherwise, if end node is a Text node and its parent's resolved value for
  241. // "white-space" is neither "pre" nor "pre-wrap" and end offset is not end node's length
  242. // and the end offsetth code unit of end node's data is a space (0x0020) or non-breaking
  243. // space (0x00A0):
  244. auto* layout_node = end_node->parent()->layout_node();
  245. if (layout_node && is<DOM::Text>(*end_node) && end_offset != end_node->length()) {
  246. auto parent_white_space = layout_node->computed_values().white_space();
  247. // FIXME: Find a way to get code points directly from the UTF-8 string
  248. auto end_node_data = *end_node->text_content();
  249. auto utf16_code_units = MUST(AK::utf8_to_utf16(end_node_data));
  250. auto offset_code_point = Utf16View { utf16_code_units }.code_point_at(end_offset);
  251. if (parent_white_space != CSS::WhiteSpace::Pre && parent_white_space != CSS::WhiteSpace::PreWrap
  252. && (offset_code_point == 0x20 || offset_code_point == 0xA0)) {
  253. // 1. If fix collapsed space is true, and collapse spaces is true, and the end offsetth
  254. // code unit of end node's data is a space (0x0020): call deleteData(end offset, 1)
  255. // on end node, then continue this loop from the beginning.
  256. if (fix_collapsed_space && collapse_spaces && offset_code_point == 0x20) {
  257. MUST(static_cast<DOM::CharacterData&>(*end_node).delete_data(end_offset, 1));
  258. continue;
  259. }
  260. // 2. Set collapse spaces to true if the end offsetth code unit of end node's data is a
  261. // space (0x0020), false otherwise.
  262. collapse_spaces = offset_code_point == 0x20;
  263. // 3. Add one to end offset.
  264. ++end_offset;
  265. // 4. Add one to length.
  266. ++length;
  267. // NOTE: We continue the loop here since we matched every condition from step 7.3
  268. continue;
  269. }
  270. }
  271. // 4. Otherwise, break from this loop.
  272. break;
  273. }
  274. // 8. If fix collapsed space is true, then while (start node, start offset) is before (end node,
  275. // end offset):
  276. if (fix_collapsed_space) {
  277. while (true) {
  278. auto relative_position = position_of_boundary_point_relative_to_other_boundary_point(*start_node, start_offset, *end_node, end_offset);
  279. if (relative_position != DOM::RelativeBoundaryPointPosition::Before)
  280. break;
  281. // 1. If end node has a child in the same editing host with index end offset − 1, set end
  282. // node to that child, then set end offset to end node's length.
  283. auto offset_minus_one_child = end_node->child_at_index(end_offset - 1);
  284. if (offset_minus_one_child && is_in_same_editing_host(end_node, *offset_minus_one_child)) {
  285. end_node = *offset_minus_one_child;
  286. end_offset = end_node->length();
  287. continue;
  288. }
  289. // 2. Otherwise, if end offset is zero and end node's parent is in the same editing host,
  290. // set end offset to end node's index, then set end node to its parent.
  291. if (end_offset == 0 && is_in_same_editing_host(end_node, *end_node->parent())) {
  292. end_offset = end_node->index();
  293. end_node = *end_node->parent();
  294. continue;
  295. }
  296. // 3. Otherwise, if end node is a Text node and its parent's resolved value for
  297. // "white-space" is neither "pre" nor "pre-wrap" and end offset is end node's length and
  298. // the last code unit of end node's data is a space (0x0020) and end node precedes a line
  299. // break:
  300. auto* layout_node = end_node->parent()->layout_node();
  301. if (layout_node && is<DOM::Text>(*end_node) && end_offset == end_node->length() && precedes_a_line_break(end_node)) {
  302. auto parent_white_space = layout_node->computed_values().white_space();
  303. if (parent_white_space != CSS::WhiteSpace::Pre && parent_white_space != CSS::WhiteSpace::PreWrap
  304. && end_node->text_content().value().ends_with_bytes(" "sv)) {
  305. // 1. Subtract one from end offset.
  306. --end_offset;
  307. // 2. Subtract one from length.
  308. --length;
  309. // 3. Call deleteData(end offset, 1) on end node.
  310. MUST(static_cast<DOM::CharacterData&>(*end_node).delete_data(end_offset, 1));
  311. // NOTE: We continue the loop here since we matched every condition from step 8.3
  312. continue;
  313. }
  314. }
  315. // 4. Otherwise, break from this loop.
  316. break;
  317. }
  318. }
  319. // 9. Let replacement whitespace be the canonical space sequence of length length. non-breaking
  320. // start is true if start offset is zero and start node follows a line break, and false
  321. // otherwise. non-breaking end is true if end offset is end node's length and end node
  322. // precedes a line break, and false otherwise.
  323. auto replacement_whitespace = canonical_space_sequence(
  324. length,
  325. start_offset == 0 && follows_a_line_break(start_node),
  326. end_offset == end_node->length() && precedes_a_line_break(end_node));
  327. // 10. While (start node, start offset) is before (end node, end offset):
  328. while (true) {
  329. auto relative_position = position_of_boundary_point_relative_to_other_boundary_point(start_node, start_offset, end_node, end_offset);
  330. if (relative_position != DOM::RelativeBoundaryPointPosition::Before)
  331. break;
  332. // 1. If start node has a child with index start offset, set start node to that child, then
  333. // set start offset to zero.
  334. if (start_node->child_at_index(start_offset)) {
  335. start_node = *start_node->child_at_index(start_offset);
  336. start_offset = 0;
  337. }
  338. // 2. Otherwise, if start node is not a Text node or if start offset is start node's length,
  339. // set start offset to one plus start node's index, then set start node to its parent.
  340. else if (!is<DOM::Text>(*start_node) || start_offset == start_node->length()) {
  341. start_offset = start_node->index() + 1;
  342. start_node = *start_node->parent();
  343. }
  344. // 3. Otherwise:
  345. else {
  346. // 1. Remove the first code unit from replacement whitespace, and let element be that
  347. // code unit.
  348. // FIXME: Find a way to get code points directly from the UTF-8 string
  349. auto replacement_whitespace_utf16 = MUST(AK::utf8_to_utf16(replacement_whitespace));
  350. auto replacement_whitespace_utf16_view = Utf16View { replacement_whitespace_utf16 };
  351. replacement_whitespace = MUST(String::from_utf16({ replacement_whitespace_utf16_view.substring_view(1) }));
  352. auto element = replacement_whitespace_utf16_view.code_point_at(0);
  353. // 2. If element is not the same as the start offsetth code unit of start node's data:
  354. auto start_node_data = *start_node->text_content();
  355. auto start_node_utf16 = MUST(AK::utf8_to_utf16(start_node_data));
  356. auto start_node_utf16_view = Utf16View { start_node_utf16 };
  357. auto start_node_code_point = start_node_utf16_view.code_point_at(start_offset);
  358. if (element != start_node_code_point) {
  359. // 1. Call insertData(start offset, element) on start node.
  360. auto& start_node_character_data = static_cast<DOM::CharacterData&>(*start_node);
  361. MUST(start_node_character_data.insert_data(start_offset, String::from_code_point(element)));
  362. // 2. Call deleteData(start offset + 1, 1) on start node.
  363. MUST(start_node_character_data.delete_data(start_offset + 1, 1));
  364. }
  365. // 3. Add one to start offset.
  366. ++start_offset;
  367. }
  368. }
  369. }
  370. // https://w3c.github.io/editing/docs/execCommand/#delete-the-selection
  371. void delete_the_selection(Selection::Selection const& selection)
  372. {
  373. // FIXME: implement the spec
  374. auto active_range = selection.range();
  375. if (!active_range)
  376. return;
  377. MUST(active_range->delete_contents());
  378. }
  379. // https://w3c.github.io/editing/docs/execCommand/#editing-host-of
  380. GC::Ptr<DOM::Node> editing_host_of_node(GC::Ref<DOM::Node> node)
  381. {
  382. // node itself, if node is an editing host;
  383. if (is_editing_host(node))
  384. return node;
  385. // or the nearest ancestor of node that is an editing host, if node is editable.
  386. if (node->is_editable()) {
  387. auto* ancestor = node->parent();
  388. do {
  389. if (is_editing_host(*ancestor))
  390. return ancestor;
  391. ancestor = ancestor->parent();
  392. } while (ancestor);
  393. VERIFY_NOT_REACHED();
  394. }
  395. // The editing host of node is null if node is neither editable nor an editing host;
  396. return {};
  397. }
  398. // https://w3c.github.io/editing/docs/execCommand/#fix-disallowed-ancestors
  399. void fix_disallowed_ancestors_of_node(GC::Ref<DOM::Node> node)
  400. {
  401. // 1. If node is not editable, abort these steps.
  402. if (!node->is_editable())
  403. return;
  404. // 2. If node is not an allowed child of any of its ancestors in the same editing host:
  405. bool allowed_child_of_any_ancestor = false;
  406. GC::Ptr<DOM::Node> ancestor = node->parent();
  407. do {
  408. if (is_in_same_editing_host(*ancestor, *node) && is_allowed_child_of_node(GC::Ref { *node }, GC::Ref { *ancestor })) {
  409. allowed_child_of_any_ancestor = true;
  410. break;
  411. }
  412. ancestor = ancestor->parent();
  413. } while (ancestor);
  414. if (!allowed_child_of_any_ancestor) {
  415. // FIXME: 1. If node is a dd or dt, wrap the one-node list consisting of node, with sibling criteria returning true for
  416. // any dl with no attributes and false otherwise, and new parent instructions returning the result of calling
  417. // createElement("dl") on the context object. Then abort these steps.
  418. // 2. If "p" is not an allowed child of the editing host of node, abort these steps.
  419. if (!is_allowed_child_of_node(HTML::TagNames::p, GC::Ref { *editing_host_of_node(*node) }))
  420. return;
  421. // 3. If node is not a prohibited paragraph child, abort these steps.
  422. if (!is_prohibited_paragraph_child(*node))
  423. return;
  424. // 4. Set the tag name of node to the default single-line container name, and let node be the result.
  425. node = set_the_tag_name(static_cast<DOM::Element&>(*node), node->document().default_single_line_container_name());
  426. // 5. Fix disallowed ancestors of node.
  427. fix_disallowed_ancestors_of_node(node);
  428. // 6. Let children be node's children.
  429. // 7. For each child in children, if child is a prohibited paragraph child:
  430. node->for_each_child([](DOM::Node& child) {
  431. if (!is_prohibited_paragraph_child(child))
  432. return IterationDecision::Continue;
  433. // 1. Record the values of the one-node list consisting of child, and let values be the result.
  434. auto values = record_the_values_of_nodes({ child });
  435. // 2. Split the parent of the one-node list consisting of child.
  436. split_the_parent_of_nodes({ child });
  437. // 3. Restore the values from values.
  438. restore_the_values_of_nodes(values);
  439. return IterationDecision::Continue;
  440. });
  441. // 8. Abort these steps.
  442. return;
  443. }
  444. // 3. Record the values of the one-node list consisting of node, and let values be the result.
  445. auto values = record_the_values_of_nodes({ *node });
  446. // 4. While node is not an allowed child of its parent, split the parent of the one-node list consisting of node.
  447. while (!is_allowed_child_of_node(GC::Ref { *node }, GC::Ref { *node->parent() }))
  448. split_the_parent_of_nodes({ *node });
  449. // 5. Restore the values from values.
  450. restore_the_values_of_nodes(values);
  451. }
  452. // https://w3c.github.io/editing/docs/execCommand/#follows-a-line-break
  453. bool follows_a_line_break(GC::Ref<DOM::Node> node)
  454. {
  455. // 1. Let offset be zero.
  456. auto offset = 0;
  457. // 2. While (node, offset) is not a block boundary point:
  458. while (!is_block_boundary_point(node, offset)) {
  459. // 1. If node has a visible child with index offset minus one, return false.
  460. auto* offset_minus_one_child = node->child_at_index(offset - 1);
  461. if (offset_minus_one_child && is_visible_node(*offset_minus_one_child))
  462. return false;
  463. // 2. If offset is zero or node has no children, set offset to node's index, then set node
  464. // to its parent.
  465. if (offset == 0 || node->child_count() == 0) {
  466. offset = node->index();
  467. node = *node->parent();
  468. }
  469. // 3. Otherwise, set node to its child with index offset minus one, then set offset to
  470. // node's length.
  471. else {
  472. node = *node->child_at_index(offset - 1);
  473. offset = node->length();
  474. }
  475. }
  476. // 3. Return true.
  477. return true;
  478. }
  479. // https://w3c.github.io/editing/docs/execCommand/#allowed-child
  480. bool is_allowed_child_of_node(Variant<GC::Ref<DOM::Node>, FlyString> child, Variant<GC::Ref<DOM::Node>, FlyString> parent)
  481. {
  482. GC::Ptr<DOM::Node> child_node;
  483. if (child.has<GC::Ref<DOM::Node>>())
  484. child_node = child.get<GC::Ref<DOM::Node>>();
  485. GC::Ptr<DOM::Node> parent_node;
  486. if (parent.has<GC::Ref<DOM::Node>>())
  487. parent_node = parent.get<GC::Ref<DOM::Node>>();
  488. if (parent.has<FlyString>() || is<DOM::Element>(parent_node.ptr())) {
  489. auto parent_local_name = parent.visit(
  490. [](FlyString local_name) { return local_name; },
  491. [](GC::Ref<DOM::Node> node) { return static_cast<DOM::Element&>(*node).local_name(); });
  492. // 1. If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or an HTML element with local name equal to
  493. // one of those, and child is a Text node whose data does not consist solely of space characters, return false.
  494. auto parent_is_table_like = parent_local_name.is_one_of(HTML::TagNames::colgroup, HTML::TagNames::table,
  495. HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::tr);
  496. if (parent_is_table_like && is<DOM::Text>(child_node.ptr())) {
  497. auto child_text_content = child_node->text_content().release_value();
  498. if (!all_of(child_text_content.bytes_as_string_view(), Infra::is_ascii_whitespace))
  499. return false;
  500. }
  501. // 2. If parent is "script", "style", "plaintext", or "xmp", or an HTML element with local name equal to one of
  502. // those, and child is not a Text node, return false.
  503. if ((child.has<FlyString>() || !is<DOM::Text>(child_node.ptr()))
  504. && parent_local_name.is_one_of(HTML::TagNames::script, HTML::TagNames::style, HTML::TagNames::plaintext, HTML::TagNames::xmp))
  505. return false;
  506. }
  507. // 3. If child is a document, DocumentFragment, or DocumentType, return false.
  508. if (is<DOM::Document>(child_node.ptr()) || is<DOM::DocumentFragment>(child_node.ptr()) || is<DOM::DocumentType>(child_node.ptr()))
  509. return false;
  510. // 4. If child is an HTML element, set child to the local name of child.
  511. if (is<HTML::HTMLElement>(child_node.ptr()))
  512. child = static_cast<DOM::Element&>(*child_node).local_name();
  513. // 5. If child is not a string, return true.
  514. if (!child.has<FlyString>())
  515. return true;
  516. auto child_local_name = child.get<FlyString>();
  517. // 6. If parent is an HTML element:
  518. if (is<HTML::HTMLElement>(parent_node.ptr())) {
  519. auto& parent_html_element = static_cast<HTML::HTMLElement&>(*parent.get<GC::Ref<DOM::Node>>());
  520. // 1. If child is "a", and parent or some ancestor of parent is an a, return false.
  521. if (child_local_name == HTML::TagNames::a) {
  522. DOM::Node* ancestor = &parent_html_element;
  523. do {
  524. if (is<DOM::Element>(ancestor) && static_cast<DOM::Element const&>(*ancestor).local_name() == HTML::TagNames::a)
  525. return false;
  526. ancestor = ancestor->parent();
  527. } while (ancestor);
  528. }
  529. // 2. If child is a prohibited paragraph child name and parent or some ancestor of parent is an element with
  530. // inline contents, return false.
  531. if (is_prohibited_paragraph_child_name(child_local_name)) {
  532. DOM::Node* ancestor = &parent_html_element;
  533. do {
  534. if (is_element_with_inline_contents(*ancestor))
  535. return false;
  536. ancestor = ancestor->parent();
  537. } while (ancestor);
  538. }
  539. // 3. If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or some ancestor of parent is an HTML
  540. // element with local name "h1", "h2", "h3", "h4", "h5", or "h6", return false.
  541. if (is_heading(child_local_name)) {
  542. DOM::Node* ancestor = &parent_html_element;
  543. do {
  544. if (is<HTML::HTMLElement>(*ancestor) && is_heading(static_cast<DOM::Element&>(*ancestor).local_name()))
  545. return false;
  546. ancestor = ancestor->parent();
  547. } while (ancestor);
  548. }
  549. // 4. Let parent be the local name of parent.
  550. parent = parent_html_element.local_name();
  551. parent_node = {};
  552. }
  553. // 7. If parent is an Element or DocumentFragment, return true.
  554. if (is<DOM::Element>(parent_node.ptr()) || is<DOM::DocumentFragment>(parent_node.ptr()))
  555. return true;
  556. // 8. If parent is not a string, return false.
  557. if (!parent.has<FlyString>())
  558. return false;
  559. auto parent_local_name = parent.get<FlyString>();
  560. // 9. If parent is on the left-hand side of an entry on the following list, then return true if child is listed on
  561. // the right-hand side of that entry, and false otherwise.
  562. // * colgroup: col
  563. if (parent_local_name == HTML::TagNames::colgroup)
  564. return child_local_name == HTML::TagNames::col;
  565. // * table: caption, col, colgroup, tbody, td, tfoot, th, thead, tr
  566. if (parent_local_name == HTML::TagNames::table) {
  567. return child_local_name.is_one_of(
  568. HTML::TagNames::caption,
  569. HTML::TagNames::col,
  570. HTML::TagNames::colgroup,
  571. HTML::TagNames::tbody,
  572. HTML::TagNames::td,
  573. HTML::TagNames::tfoot,
  574. HTML::TagNames::th,
  575. HTML::TagNames::thead,
  576. HTML::TagNames::tr);
  577. }
  578. // * tbody, tfoot, thead: td, th, tr
  579. if (parent_local_name.is_one_of(HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead))
  580. return child_local_name.is_one_of(HTML::TagNames::td, HTML::TagNames::th, HTML::TagNames::tr);
  581. // * tr: td, th
  582. if (parent_local_name == HTML::TagNames::tr)
  583. return child_local_name.is_one_of(HTML::TagNames::td, HTML::TagNames::th);
  584. // * dl: dt, dd
  585. if (parent_local_name == HTML::TagNames::dl)
  586. return child_local_name.is_one_of(HTML::TagNames::dt, HTML::TagNames::dd);
  587. // * dir, ol, ul: dir, li, ol, ul
  588. if (parent_local_name.is_one_of(HTML::TagNames::dir, HTML::TagNames::ol, HTML::TagNames::ul))
  589. return child_local_name.is_one_of(HTML::TagNames::dir, HTML::TagNames::li, HTML::TagNames::ol, HTML::TagNames::ul);
  590. // * hgroup: h1, h2, h3, h4, h5, h6
  591. if (parent_local_name == HTML::TagNames::hgroup)
  592. return is_heading(child_local_name);
  593. // 10. If child is "body", "caption", "col", "colgroup", "frame", "frameset", "head", "html", "tbody", "td",
  594. // "tfoot", "th", "thead", or "tr", return false.
  595. if (child_local_name.is_one_of(
  596. HTML::TagNames::body,
  597. HTML::TagNames::caption,
  598. HTML::TagNames::col,
  599. HTML::TagNames::colgroup,
  600. HTML::TagNames::frame,
  601. HTML::TagNames::frameset,
  602. HTML::TagNames::head,
  603. HTML::TagNames::html,
  604. HTML::TagNames::tbody,
  605. HTML::TagNames::td,
  606. HTML::TagNames::tfoot,
  607. HTML::TagNames::th,
  608. HTML::TagNames::thead,
  609. HTML::TagNames::tr))
  610. return false;
  611. // 11. If child is "dd" or "dt" and parent is not "dl", return false.
  612. if (child_local_name.is_one_of(HTML::TagNames::dd, HTML::TagNames::dt) && parent_local_name != HTML::TagNames::dl)
  613. return false;
  614. // 12. If child is "li" and parent is not "ol" or "ul", return false.
  615. if (child_local_name == HTML::TagNames::li && parent_local_name != HTML::TagNames::ol && parent_local_name != HTML::TagNames::ul)
  616. return false;
  617. // 13. If parent is on the left-hand side of an entry on the following list and child is listed on the right-hand
  618. // side of that entry, return false.
  619. // * a: a
  620. if (parent_local_name == HTML::TagNames::a && child_local_name == HTML::TagNames::a)
  621. return false;
  622. // * dd, dt: dd, dt
  623. if (parent_local_name.is_one_of(HTML::TagNames::dd, HTML::TagNames::dt)
  624. && child_local_name.is_one_of(HTML::TagNames::dd, HTML::TagNames::dt))
  625. return false;
  626. // * h1, h2, h3, h4, h5, h6: h1, h2, h3, h4, h5, h6
  627. if (is_heading(parent_local_name) && is_heading(child_local_name))
  628. return false;
  629. // * li: li
  630. if (parent_local_name == HTML::TagNames::li && child_local_name == HTML::TagNames::li)
  631. return false;
  632. // * nobr: nobr
  633. if (parent_local_name == HTML::TagNames::nobr && child_local_name == HTML::TagNames::nobr)
  634. return false;
  635. // * All names of an element with inline contents: all prohibited paragraph child names
  636. if (is_name_of_an_element_with_inline_contents(parent_local_name) && is_prohibited_paragraph_child_name(child_local_name))
  637. return false;
  638. // * td, th: caption, col, colgroup, tbody, td, tfoot, th, thead, tr
  639. if (parent_local_name.is_one_of(HTML::TagNames::td, HTML::TagNames::th)
  640. && child_local_name.is_one_of(
  641. HTML::TagNames::caption,
  642. HTML::TagNames::col,
  643. HTML::TagNames::colgroup,
  644. HTML::TagNames::tbody,
  645. HTML::TagNames::td,
  646. HTML::TagNames::tfoot,
  647. HTML::TagNames::th,
  648. HTML::TagNames::thead,
  649. HTML::TagNames::tr))
  650. return false;
  651. // 14. Return true.
  652. return true;
  653. }
  654. // https://w3c.github.io/editing/docs/execCommand/#block-boundary-point
  655. bool is_block_boundary_point(GC::Ref<DOM::Node> node, u32 offset)
  656. {
  657. // A boundary point is a block boundary point if it is either a block start point or a block end point.
  658. return is_block_start_point(node, offset) || is_block_end_point(node, offset);
  659. }
  660. // https://w3c.github.io/editing/docs/execCommand/#block-end-point
  661. bool is_block_end_point(GC::Ref<DOM::Node> node, u32 offset)
  662. {
  663. // A boundary point (node, offset) is a block end point if either node's parent is null and
  664. // offset is node's length;
  665. if (!node->parent() && offset == node->length())
  666. return true;
  667. // or node has a child with index offset, and that child is a visible block node.
  668. auto offset_child = node->child_at_index(offset);
  669. return offset_child && is_visible_node(*offset_child) && is_block_node(*offset_child);
  670. }
  671. // https://w3c.github.io/editing/docs/execCommand/#block-node
  672. bool is_block_node(GC::Ref<DOM::Node> node)
  673. {
  674. // A block node is either an Element whose "display" property does not have resolved value
  675. // "inline" or "inline-block" or "inline-table" or "none", or a document, or a DocumentFragment.
  676. if (is<DOM::Document>(*node) || is<DOM::DocumentFragment>(*node))
  677. return true;
  678. auto layout_node = node->layout_node();
  679. if (!layout_node)
  680. return false;
  681. auto display = layout_node->display();
  682. return is<DOM::Element>(*node)
  683. && !(display.is_inline_outside() && (display.is_flow_inside() || display.is_flow_root_inside() || display.is_table_inside()))
  684. && !display.is_none();
  685. }
  686. // https://w3c.github.io/editing/docs/execCommand/#block-start-point
  687. bool is_block_start_point(GC::Ref<DOM::Node> node, u32 offset)
  688. {
  689. // A boundary point (node, offset) is a block start point if either node's parent is null and
  690. // offset is zero;
  691. if (!node->parent() && offset == 0)
  692. return true;
  693. // or node has a child with index offset − 1, and that child is either a visible block node or a
  694. // visible br.
  695. auto offset_minus_one_child = node->child_at_index(offset - 1);
  696. if (!offset_minus_one_child)
  697. return false;
  698. return is_visible_node(*offset_minus_one_child)
  699. && (is_block_node(*offset_minus_one_child) || is<HTML::HTMLBRElement>(*offset_minus_one_child));
  700. }
  701. // https://w3c.github.io/editing/docs/execCommand/#collapsed-whitespace-node
  702. bool is_collapsed_whitespace_node(GC::Ref<DOM::Node> node)
  703. {
  704. // 1. If node is not a whitespace node, return false.
  705. if (!is_whitespace_node(node))
  706. return false;
  707. // 2. If node's data is the empty string, return true.
  708. auto node_data = node->text_content();
  709. if (!node_data.has_value() || node_data->is_empty())
  710. return true;
  711. // 3. Let ancestor be node's parent.
  712. GC::Ptr<DOM::Node> ancestor = node->parent();
  713. // 4. If ancestor is null, return true.
  714. if (!ancestor)
  715. return true;
  716. // 5. If the "display" property of some ancestor of node has resolved value "none", return true.
  717. if (ancestor->layout_node() && ancestor->layout_node()->display().is_none())
  718. return true;
  719. // 6. While ancestor is not a block node and its parent is not null, set ancestor to its parent.
  720. while (!is_block_node(*ancestor) && ancestor->parent())
  721. ancestor = ancestor->parent();
  722. // 7. Let reference be node.
  723. auto reference = node;
  724. // 8. While reference is a descendant of ancestor:
  725. while (reference->is_descendant_of(*ancestor)) {
  726. // 1. Let reference be the node before it in tree order.
  727. reference = *reference->previous_in_pre_order();
  728. // 2. If reference is a block node or a br, return true.
  729. if (is_block_node(reference) || is<HTML::HTMLBRElement>(*reference))
  730. return true;
  731. // 3. If reference is a Text node that is not a whitespace node, or is an img, break from
  732. // this loop.
  733. if ((is<DOM::Text>(*reference) && !is_whitespace_node(reference)) || is<HTML::HTMLImageElement>(*reference))
  734. break;
  735. }
  736. // 9. Let reference be node.
  737. reference = node;
  738. // 10. While reference is a descendant of ancestor:
  739. while (reference->is_descendant_of(*ancestor)) {
  740. // 1. Let reference be the node after it in tree order, or null if there is no such node.
  741. reference = *reference->next_in_pre_order();
  742. // 2. If reference is a block node or a br, return true.
  743. if (is_block_node(reference) || is<HTML::HTMLBRElement>(*reference))
  744. return true;
  745. // 3. If reference is a Text node that is not a whitespace node, or is an img, break from
  746. // this loop.
  747. if ((is<DOM::Text>(*reference) && !is_whitespace_node(reference)) || is<HTML::HTMLImageElement>(*reference))
  748. break;
  749. }
  750. // 11. Return false.
  751. return false;
  752. }
  753. // https://html.spec.whatwg.org/multipage/interaction.html#editing-host
  754. bool is_editing_host(GC::Ref<DOM::Node> node)
  755. {
  756. // An editing host is either an HTML element with its contenteditable attribute in the true
  757. // state or plaintext-only state, or a child HTML element of a Document whose design mode
  758. // enabled is true.
  759. if (!is<HTML::HTMLElement>(*node))
  760. return false;
  761. auto const& html_element = static_cast<HTML::HTMLElement&>(*node);
  762. return html_element.content_editable_state() == HTML::ContentEditableState::True
  763. || html_element.content_editable_state() == HTML::ContentEditableState::PlaintextOnly
  764. || node->document().design_mode_enabled_state();
  765. }
  766. // https://w3c.github.io/editing/docs/execCommand/#element-with-inline-contents
  767. bool is_element_with_inline_contents(GC::Ref<DOM::Node> node)
  768. {
  769. // An element with inline contents is an HTML element whose local name is a name of an element with inline contents.
  770. return is<HTML::HTMLElement>(*node)
  771. && is_name_of_an_element_with_inline_contents(static_cast<DOM::Element&>(*node).local_name());
  772. }
  773. // https://w3c.github.io/editing/docs/execCommand/#extraneous-line-break
  774. bool is_extraneous_line_break(GC::Ref<DOM::Node> node)
  775. {
  776. // An extraneous line break is a br
  777. if (!is<HTML::HTMLBRElement>(*node))
  778. return false;
  779. // ...except that a br that is the sole child of an li is not extraneous.
  780. auto parent = node->parent();
  781. if (parent && static_cast<DOM::Element&>(*parent).local_name() == HTML::TagNames::li && parent->child_count() == 1)
  782. return false;
  783. // FIXME: ...that has no visual effect, in that removing it from the DOM
  784. // would not change layout,
  785. return false;
  786. }
  787. // https://w3c.github.io/editing/docs/execCommand/#in-the-same-editing-host
  788. bool is_in_same_editing_host(GC::Ref<DOM::Node> node_a, GC::Ref<DOM::Node> node_b)
  789. {
  790. // Two nodes are in the same editing host if the editing host of the first is non-null and the
  791. // same as the editing host of the second.
  792. auto editing_host_a = editing_host_of_node(node_a);
  793. auto editing_host_b = editing_host_of_node(node_b);
  794. return editing_host_a && editing_host_a == editing_host_b;
  795. }
  796. // https://w3c.github.io/editing/docs/execCommand/#inline-node
  797. bool is_inline_node(GC::Ref<DOM::Node> node)
  798. {
  799. // An inline node is a node that is not a block node.
  800. return !is_block_node(node);
  801. }
  802. // https://w3c.github.io/editing/docs/execCommand/#invisible
  803. bool is_invisible_node(GC::Ref<DOM::Node> node)
  804. {
  805. // Something is invisible if it is a node that is not visible.
  806. return !is_visible_node(node);
  807. }
  808. // https://w3c.github.io/editing/docs/execCommand/#name-of-an-element-with-inline-contents
  809. bool is_name_of_an_element_with_inline_contents(FlyString const& local_name)
  810. {
  811. // A name of an element with inline contents is "a", "abbr", "b", "bdi", "bdo", "cite", "code", "dfn", "em", "h1",
  812. // "h2", "h3", "h4", "h5", "h6", "i", "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",
  813. // "span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike", "xmp", "big", "blink", "font",
  814. // "marquee", "nobr", or "tt".
  815. return local_name.is_one_of(
  816. HTML::TagNames::a,
  817. HTML::TagNames::abbr,
  818. HTML::TagNames::b,
  819. HTML::TagNames::bdi,
  820. HTML::TagNames::bdo,
  821. HTML::TagNames::cite,
  822. HTML::TagNames::code,
  823. HTML::TagNames::dfn,
  824. HTML::TagNames::em,
  825. HTML::TagNames::h1,
  826. HTML::TagNames::h2,
  827. HTML::TagNames::h3,
  828. HTML::TagNames::h4,
  829. HTML::TagNames::h5,
  830. HTML::TagNames::h6,
  831. HTML::TagNames::i,
  832. HTML::TagNames::kbd,
  833. HTML::TagNames::mark,
  834. HTML::TagNames::p,
  835. HTML::TagNames::pre,
  836. HTML::TagNames::q,
  837. HTML::TagNames::rp,
  838. HTML::TagNames::rt,
  839. HTML::TagNames::ruby,
  840. HTML::TagNames::s,
  841. HTML::TagNames::samp,
  842. HTML::TagNames::small,
  843. HTML::TagNames::span,
  844. HTML::TagNames::strong,
  845. HTML::TagNames::sub,
  846. HTML::TagNames::sup,
  847. HTML::TagNames::u,
  848. HTML::TagNames::var,
  849. HTML::TagNames::acronym,
  850. HTML::TagNames::listing,
  851. HTML::TagNames::strike,
  852. HTML::TagNames::xmp,
  853. HTML::TagNames::big,
  854. HTML::TagNames::blink,
  855. HTML::TagNames::font,
  856. HTML::TagNames::marquee,
  857. HTML::TagNames::nobr,
  858. HTML::TagNames::tt);
  859. }
  860. // https://w3c.github.io/editing/docs/execCommand/#non-list-single-line-container
  861. bool is_non_list_single_line_container(GC::Ref<DOM::Node> node)
  862. {
  863. // A non-list single-line container is an HTML element with local name "address", "divis_", "h1", "h2", "h3", "h4",
  864. // "h5", "h6", "listing", "p", "pre", or "xmp".
  865. if (!is<HTML::HTMLElement>(*node))
  866. return false;
  867. auto& local_name = static_cast<HTML::HTMLElement&>(*node).local_name();
  868. return is_heading(local_name)
  869. || local_name.is_one_of(HTML::TagNames::address, HTML::TagNames::div, HTML::TagNames::listing,
  870. HTML::TagNames::p, HTML::TagNames::pre, HTML::TagNames::xmp);
  871. }
  872. // https://w3c.github.io/editing/docs/execCommand/#prohibited-paragraph-child
  873. bool is_prohibited_paragraph_child(GC::Ref<DOM::Node> node)
  874. {
  875. // A prohibited paragraph child is an HTML element whose local name is a prohibited paragraph child name.
  876. return is<HTML::HTMLElement>(*node) && is_prohibited_paragraph_child_name(static_cast<DOM::Element&>(*node).local_name());
  877. }
  878. // https://w3c.github.io/editing/docs/execCommand/#prohibited-paragraph-child-name
  879. bool is_prohibited_paragraph_child_name(FlyString const& local_name)
  880. {
  881. // A prohibited paragraph child name is "address", "article", "aside", "blockquote", "caption", "center", "col",
  882. // "colgroup", "dd", "details", "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form",
  883. // "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li", "listing", "menu", "nav", "ol", "p",
  884. // "plaintext", "pre", "section", "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or "xmp".
  885. return local_name.is_one_of(
  886. HTML::TagNames::address,
  887. HTML::TagNames::article,
  888. HTML::TagNames::aside,
  889. HTML::TagNames::blockquote,
  890. HTML::TagNames::caption,
  891. HTML::TagNames::center,
  892. HTML::TagNames::col,
  893. HTML::TagNames::colgroup,
  894. HTML::TagNames::dd,
  895. HTML::TagNames::details,
  896. HTML::TagNames::dir,
  897. HTML::TagNames::div,
  898. HTML::TagNames::dl,
  899. HTML::TagNames::dt,
  900. HTML::TagNames::fieldset,
  901. HTML::TagNames::figcaption,
  902. HTML::TagNames::figure,
  903. HTML::TagNames::footer,
  904. HTML::TagNames::form,
  905. HTML::TagNames::h1,
  906. HTML::TagNames::h2,
  907. HTML::TagNames::h3,
  908. HTML::TagNames::h4,
  909. HTML::TagNames::h5,
  910. HTML::TagNames::h6,
  911. HTML::TagNames::header,
  912. HTML::TagNames::hgroup,
  913. HTML::TagNames::hr,
  914. HTML::TagNames::li,
  915. HTML::TagNames::listing,
  916. HTML::TagNames::menu,
  917. HTML::TagNames::nav,
  918. HTML::TagNames::ol,
  919. HTML::TagNames::p,
  920. HTML::TagNames::plaintext,
  921. HTML::TagNames::pre,
  922. HTML::TagNames::section,
  923. HTML::TagNames::summary,
  924. HTML::TagNames::table,
  925. HTML::TagNames::tbody,
  926. HTML::TagNames::td,
  927. HTML::TagNames::tfoot,
  928. HTML::TagNames::th,
  929. HTML::TagNames::thead,
  930. HTML::TagNames::tr,
  931. HTML::TagNames::ul,
  932. HTML::TagNames::xmp);
  933. }
  934. // https://w3c.github.io/editing/docs/execCommand/#single-line-container
  935. bool is_single_line_container(GC::Ref<DOM::Node> node)
  936. {
  937. // A single-line container is either a non-list single-line container, or an HTML element with local name "li",
  938. // "dt", or "dd".
  939. if (is_non_list_single_line_container(node))
  940. return true;
  941. if (!is<HTML::HTMLElement>(*node))
  942. return false;
  943. auto& html_element = static_cast<HTML::HTMLElement&>(*node);
  944. return html_element.local_name().is_one_of(HTML::TagNames::li, HTML::TagNames::dt, HTML::TagNames::dd);
  945. }
  946. // https://w3c.github.io/editing/docs/execCommand/#visible
  947. bool is_visible_node(GC::Ref<DOM::Node> node)
  948. {
  949. // excluding any node with an inclusive ancestor Element whose "display" property has resolved
  950. // value "none".
  951. GC::Ptr<DOM::Node> inclusive_ancestor = node;
  952. do {
  953. auto* layout_node = inclusive_ancestor->layout_node();
  954. if (layout_node && layout_node->display().is_none())
  955. return false;
  956. inclusive_ancestor = inclusive_ancestor->parent();
  957. } while (inclusive_ancestor);
  958. // Something is visible if it is a node that either is a block node,
  959. if (is_block_node(node))
  960. return true;
  961. // or a Text node that is not a collapsed whitespace node,
  962. if (is<DOM::Text>(*node) && !is_collapsed_whitespace_node(node))
  963. return true;
  964. // or an img,
  965. if (is<HTML::HTMLImageElement>(*node))
  966. return true;
  967. // or a br that is not an extraneous line break,
  968. if (is<HTML::HTMLBRElement>(*node) && !is_extraneous_line_break(node))
  969. return true;
  970. // or any node with a visible descendant;
  971. // NOTE: We call into is_visible_node() recursively, so check children instead of descendants.
  972. bool has_visible_child_node = false;
  973. node->for_each_child([&](DOM::Node& child_node) {
  974. if (is_visible_node(child_node)) {
  975. has_visible_child_node = true;
  976. return IterationDecision::Break;
  977. }
  978. return IterationDecision::Continue;
  979. });
  980. return has_visible_child_node;
  981. }
  982. // https://w3c.github.io/editing/docs/execCommand/#whitespace-node
  983. bool is_whitespace_node(GC::Ref<DOM::Node> node)
  984. {
  985. // NOTE: All constraints below check that node is a Text node
  986. if (!is<DOM::Text>(*node))
  987. return false;
  988. // A whitespace node is either a Text node whose data is the empty string;
  989. auto& character_data = static_cast<DOM::CharacterData&>(*node);
  990. if (character_data.data().is_empty())
  991. return true;
  992. // NOTE: All constraints below require a parent Element with a resolved value for "white-space"
  993. GC::Ptr<DOM::Node> parent = node->parent();
  994. if (!is<DOM::Element>(parent.ptr()))
  995. return false;
  996. auto* layout_node = parent->layout_node();
  997. if (!layout_node)
  998. return false;
  999. auto white_space = layout_node->computed_values().white_space();
  1000. // or a Text node whose data consists only of one or more tabs (0x0009), line feeds (0x000A),
  1001. // carriage returns (0x000D), and/or spaces (0x0020), and whose parent is an Element whose
  1002. // resolved value for "white-space" is "normal" or "nowrap";
  1003. auto is_tab_lf_cr_or_space = [](u32 codepoint) {
  1004. return codepoint == '\t' || codepoint == '\n' || codepoint == '\r' || codepoint == ' ';
  1005. };
  1006. auto code_points = character_data.data().code_points();
  1007. if (all_of(code_points, is_tab_lf_cr_or_space) && (white_space == CSS::WhiteSpace::Normal || white_space == CSS::WhiteSpace::Nowrap))
  1008. return true;
  1009. // or a Text node whose data consists only of one or more tabs (0x0009), carriage returns
  1010. // (0x000D), and/or spaces (0x0020), and whose parent is an Element whose resolved value for
  1011. // "white-space" is "pre-line".
  1012. auto is_tab_cr_or_space = [](u32 codepoint) {
  1013. return codepoint == '\t' || codepoint == '\r' || codepoint == ' ';
  1014. };
  1015. if (all_of(code_points, is_tab_cr_or_space) && white_space == CSS::WhiteSpace::PreLine)
  1016. return true;
  1017. return false;
  1018. }
  1019. // https://w3c.github.io/editing/docs/execCommand/#preserving-ranges
  1020. void move_node_preserving_ranges(GC::Ref<DOM::Node> node, GC::Ref<DOM::Node> new_parent, u32 new_index)
  1021. {
  1022. // To move a node to a new location, preserving ranges, remove the node from its original parent
  1023. // (if any), then insert it in the new location. In doing so, follow these rules instead of
  1024. // those defined by the insert and remove algorithms:
  1025. // FIXME: Currently this is a simple range-destroying move. Implement "follow these rules" as
  1026. // described above.
  1027. // 1. Let node be the moved node, old parent and old index be the old parent (which may be null)
  1028. // and index, and new parent and new index be the new parent and index.
  1029. auto* old_parent = node->parent();
  1030. [[maybe_unused]] auto old_index = node->index();
  1031. if (old_parent)
  1032. node->remove();
  1033. auto* new_next_sibling = new_parent->child_at_index(new_index);
  1034. new_parent->insert_before(node, new_next_sibling);
  1035. // FIXME: 2. If a boundary point's node is the same as or a descendant of node, leave it unchanged, so
  1036. // it moves to the new location.
  1037. // FIXME: 3. If a boundary point's node is new parent and its offset is greater than new index, add one
  1038. // to its offset.
  1039. // FIXME: 4. If a boundary point's node is old parent and its offset is old index or old index + 1, set
  1040. // its node to new parent and add new index − old index to its offset.
  1041. // FIXME: 5. If a boundary point's node is old parent and its offset is greater than old index + 1,
  1042. // subtract one from its offset.
  1043. }
  1044. // https://w3c.github.io/editing/docs/execCommand/#normalize-sublists
  1045. void normalize_sublists_in_node(GC::Ref<DOM::Element> item)
  1046. {
  1047. // 1. If item is not an li or it is not editable or its parent is not editable, abort these
  1048. // steps.
  1049. if (item->local_name() != HTML::TagNames::li || !item->is_editable() || !item->parent()->is_editable())
  1050. return;
  1051. // 2. Let new item be null.
  1052. GC::Ptr<DOM::Element> new_item;
  1053. // 3. While item has an ol or ul child:
  1054. while (item->has_child_of_type<HTML::HTMLOListElement>() || item->has_child_of_type<HTML::HTMLUListElement>()) {
  1055. // 1. Let child be the last child of item.
  1056. GC::Ref<DOM::Node> child = *item->last_child();
  1057. // 2. If child is an ol or ul, or new item is null and child is a Text node whose data
  1058. // consists of zero of more space characters:
  1059. auto child_text = child->text_content();
  1060. auto text_is_all_whitespace = child_text.has_value() && all_of(child_text.value().bytes_as_string_view(), Infra::is_ascii_whitespace);
  1061. if ((is<HTML::HTMLOListElement>(*child) || is<HTML::HTMLUListElement>(*child))
  1062. || (!new_item && is<DOM::Text>(*child) && text_is_all_whitespace)) {
  1063. // 1. Set new item to null.
  1064. new_item = {};
  1065. // 2. Insert child into the parent of item immediately following item, preserving
  1066. // ranges.
  1067. move_node_preserving_ranges(child, *item->parent(), item->index());
  1068. }
  1069. // 3. Otherwise:
  1070. else {
  1071. // 1. If new item is null, let new item be the result of calling createElement("li") on
  1072. // the ownerDocument of item, then insert new item into the parent of item
  1073. // immediately after item.
  1074. if (!new_item) {
  1075. new_item = MUST(DOM::create_element(*item->owner_document(), HTML::TagNames::li, Namespace::HTML));
  1076. item->parent()->insert_before(*new_item, item->next_sibling());
  1077. }
  1078. // 2. Insert child into new item as its first child, preserving ranges.
  1079. move_node_preserving_ranges(child, *new_item, 0);
  1080. }
  1081. }
  1082. }
  1083. // https://w3c.github.io/editing/docs/execCommand/#precedes-a-line-break
  1084. bool precedes_a_line_break(GC::Ref<DOM::Node> node)
  1085. {
  1086. // 1. Let offset be node's length.
  1087. auto offset = node->length();
  1088. // 2. While (node, offset) is not a block boundary point:
  1089. while (!is_block_boundary_point(node, offset)) {
  1090. // 1. If node has a visible child with index offset, return false.
  1091. auto* offset_child = node->child_at_index(offset);
  1092. if (offset_child && is_visible_node(*offset_child))
  1093. return false;
  1094. // 2. If offset is node's length or node has no children, set offset to one plus node's
  1095. // index, then set node to its parent.
  1096. if (offset == node->length() || node->child_count() == 0) {
  1097. offset = node->index() + 1;
  1098. node = *node->parent();
  1099. }
  1100. // 3. Otherwise, set node to its child with index offset and set offset to zero.
  1101. else {
  1102. node = *node->child_at_index(offset);
  1103. offset = 0;
  1104. }
  1105. }
  1106. // 3. Return true;
  1107. return true;
  1108. }
  1109. // https://w3c.github.io/editing/docs/execCommand/#record-the-values
  1110. Vector<RecordedNodeValue> record_the_values_of_nodes(Vector<GC::Ref<DOM::Node>> const& node_list)
  1111. {
  1112. // 1. Let values be a list of (node, command, specified command value) triples, initially empty.
  1113. Vector<RecordedNodeValue> values;
  1114. // 2. For each node in node list, for each command in the list "subscript", "bold", "fontName",
  1115. // "fontSize", "foreColor", "hiliteColor", "italic", "strikethrough", and "underline" in that
  1116. // order:
  1117. Array const commands = { CommandNames::subscript, CommandNames::bold, CommandNames::fontName,
  1118. CommandNames::fontSize, CommandNames::foreColor, CommandNames::hiliteColor, CommandNames::italic,
  1119. CommandNames::strikethrough, CommandNames::underline };
  1120. for (auto node : node_list) {
  1121. for (auto command : commands) {
  1122. // 1. Let ancestor equal node.
  1123. auto ancestor = node;
  1124. // 2. If ancestor is not an Element, set it to its parent.
  1125. if (!is<DOM::Element>(*ancestor))
  1126. ancestor = *ancestor->parent();
  1127. // 3. While ancestor is an Element and its specified command value for command is null, set
  1128. // it to its parent.
  1129. while (is<DOM::Element>(*ancestor) && !specified_command_value(static_cast<DOM::Element&>(*ancestor), command).has_value())
  1130. ancestor = *ancestor->parent();
  1131. // 4. If ancestor is an Element, add (node, command, ancestor's specified command value for
  1132. // command) to values. Otherwise add (node, command, null) to values.
  1133. if (is<DOM::Element>(*ancestor))
  1134. values.empend(*node, command, specified_command_value(static_cast<DOM::Element&>(*ancestor), command));
  1135. else
  1136. values.empend(*node, command, OptionalNone {});
  1137. }
  1138. }
  1139. // 3. Return values.
  1140. return values;
  1141. }
  1142. // https://w3c.github.io/editing/docs/execCommand/#remove-extraneous-line-breaks-at-the-end-of
  1143. void remove_extraneous_line_breaks_at_the_end_of_node(GC::Ref<DOM::Node> node)
  1144. {
  1145. // 1. Let ref be node.
  1146. GC::Ptr<DOM::Node> ref = node;
  1147. // 2. While ref has children, set ref to its lastChild.
  1148. while (ref->child_count() > 0)
  1149. ref = ref->last_child();
  1150. // 3. While ref is invisible but not an extraneous line break, and ref does not equal node, set
  1151. // ref to the node before it in tree order.
  1152. while (is_invisible_node(*ref)
  1153. && !is_extraneous_line_break(*ref)
  1154. && ref != node) {
  1155. ref = ref->previous_in_pre_order();
  1156. }
  1157. // 4. If ref is an editable extraneous line break:
  1158. if (ref->is_editable() && is_extraneous_line_break(*ref)) {
  1159. // 1. While ref's parent is editable and invisible, set ref to its parent.
  1160. while (ref->parent()->is_editable() && is_invisible_node(*ref->parent()))
  1161. ref = ref->parent();
  1162. // 2. Remove ref from its parent.
  1163. ref->remove();
  1164. }
  1165. }
  1166. // https://w3c.github.io/editing/docs/execCommand/#remove-extraneous-line-breaks-before
  1167. void remove_extraneous_line_breaks_before_node(GC::Ref<DOM::Node> node)
  1168. {
  1169. // 1. Let ref be the previousSibling of node.
  1170. GC::Ptr<DOM::Node> ref = node->previous_sibling();
  1171. // 2. If ref is null, abort these steps.
  1172. if (!ref)
  1173. return;
  1174. // 3. While ref has children, set ref to its lastChild.
  1175. while (ref->child_count() > 0)
  1176. ref = ref->last_child();
  1177. // 4. While ref is invisible but not an extraneous line break, and ref does not equal node's
  1178. // parent, set ref to the node before it in tree order.
  1179. while (is_invisible_node(*ref)
  1180. && !is_extraneous_line_break(*ref)
  1181. && ref != node->parent()) {
  1182. ref = ref->previous_in_pre_order();
  1183. }
  1184. // 5. If ref is an editable extraneous line break, remove it from its parent.
  1185. if (ref->is_editable() && is_extraneous_line_break(*ref))
  1186. ref->remove();
  1187. }
  1188. // https://w3c.github.io/editing/docs/execCommand/#remove-extraneous-line-breaks-from
  1189. void remove_extraneous_line_breaks_from_a_node(GC::Ref<DOM::Node> node)
  1190. {
  1191. // To remove extraneous line breaks from a node, first remove extraneous line breaks before it,
  1192. // then remove extraneous line breaks at the end of it.
  1193. remove_extraneous_line_breaks_before_node(node);
  1194. remove_extraneous_line_breaks_at_the_end_of_node(node);
  1195. }
  1196. // https://w3c.github.io/editing/docs/execCommand/#preserving-its-descendants
  1197. void remove_node_preserving_its_descendants(GC::Ref<DOM::Node> node)
  1198. {
  1199. // To remove a node node while preserving its descendants, split the parent of node's children
  1200. // if it has any.
  1201. if (node->child_count() > 0) {
  1202. Vector<GC::Ref<DOM::Node>> children;
  1203. children.ensure_capacity(node->child_count());
  1204. for (auto* child = node->first_child(); child; child = child->next_sibling())
  1205. children.append(*child);
  1206. split_the_parent_of_nodes(move(children));
  1207. return;
  1208. }
  1209. // If it has no children, instead remove it from its parent.
  1210. node->remove();
  1211. }
  1212. // https://w3c.github.io/editing/docs/execCommand/#restore-the-values
  1213. void restore_the_values_of_nodes(Vector<RecordedNodeValue> const& values)
  1214. {
  1215. // 1. For each (node, command, value) triple in values:
  1216. for (auto& recorded_node_value : values) {
  1217. // 1. Let ancestor equal node.
  1218. GC::Ptr<DOM::Node> ancestor = recorded_node_value.node;
  1219. // 2. If ancestor is not an Element, set it to its parent.
  1220. if (!is<DOM::Element>(*ancestor))
  1221. ancestor = *ancestor->parent();
  1222. // 3. While ancestor is an Element and its specified command value for command is null, set it to its parent.
  1223. auto const& command = recorded_node_value.command;
  1224. while (is<DOM::Element>(*ancestor) && !specified_command_value(static_cast<DOM::Element&>(*ancestor), command).has_value())
  1225. ancestor = *ancestor->parent();
  1226. // FIXME: 4. If value is null and ancestor is an Element, push down values on node for command, with new value null.
  1227. // FIXME: 5. Otherwise, if ancestor is an Element and its specified command value for command is not equivalent to
  1228. // value, or if ancestor is not an Element and value is not null, force the value of command to value on
  1229. // node.
  1230. }
  1231. }
  1232. // https://w3c.github.io/editing/docs/execCommand/#set-the-tag-name
  1233. GC::Ref<DOM::Element> set_the_tag_name(GC::Ref<DOM::Element> element, FlyString const& new_name)
  1234. {
  1235. // 1. If element is an HTML element with local name equal to new name, return element.
  1236. if (is<HTML::HTMLElement>(*element) && static_cast<DOM::Element&>(element).local_name() == new_name)
  1237. return element;
  1238. // 2. If element's parent is null, return element.
  1239. if (!element->parent())
  1240. return element;
  1241. // 3. Let replacement element be the result of calling createElement(new name) on the ownerDocument of element.
  1242. auto replacement_element = MUST(element->owner_document()->create_element(new_name.to_string(), DOM::ElementCreationOptions {}));
  1243. // 4. Insert replacement element into element's parent immediately before element.
  1244. element->parent()->insert_before(replacement_element, element);
  1245. // 5. Copy all attributes of element to replacement element, in order.
  1246. element->for_each_attribute([&replacement_element](FlyString const& name, String const& value) {
  1247. MUST(replacement_element->set_attribute(name, value));
  1248. });
  1249. // 6. While element has children, append the first child of element as the last child of replacement element, preserving ranges.
  1250. while (element->has_children())
  1251. move_node_preserving_ranges(*element->first_child(), *replacement_element, replacement_element->child_count());
  1252. // 7. Remove element from its parent.
  1253. element->remove();
  1254. // 8. Return replacement element.
  1255. return replacement_element;
  1256. }
  1257. // https://w3c.github.io/editing/docs/execCommand/#specified-command-value
  1258. Optional<String> specified_command_value(GC::Ref<DOM::Element> element, FlyString const& command)
  1259. {
  1260. // 1. If command is "backColor" or "hiliteColor" and the Element's display property does not have resolved value "inline", return null.
  1261. auto layout_node = element->layout_node();
  1262. if ((command == CommandNames::backColor || command == CommandNames::hiliteColor) && layout_node) {
  1263. if (layout_node->computed_values().display().is_inline_outside())
  1264. return {};
  1265. }
  1266. // 2. If command is "createLink" or "unlink":
  1267. if (command == CommandNames::createLink || command == CommandNames::unlink) {
  1268. // 1. If element is an a element and has an href attribute, return the value of that attribute.
  1269. auto href_attribute = element->get_attribute(HTML::AttributeNames::href);
  1270. if (href_attribute.has_value())
  1271. return href_attribute.release_value();
  1272. // 2. Return null.
  1273. return {};
  1274. }
  1275. // 3. If command is "subscript" or "superscript":
  1276. if (command == CommandNames::subscript || command == CommandNames::superscript) {
  1277. // 1. If element is a sup, return "superscript".
  1278. if (element->local_name() == HTML::TagNames::sup)
  1279. return "superscript"_string;
  1280. // 2. If element is a sub, return "subscript".
  1281. if (element->local_name() == HTML::TagNames::sub)
  1282. return "subscript"_string;
  1283. // 3. Return null.
  1284. return {};
  1285. }
  1286. // FIXME: 4. If command is "strikethrough", and element has a style attribute set, and that attribute sets "text-decoration":
  1287. if (false) {
  1288. // FIXME: 1. If element's style attribute sets "text-decoration" to a value containing "line-through", return "line-through".
  1289. // 2. Return null.
  1290. return {};
  1291. }
  1292. // 5. If command is "strikethrough" and element is an s or strike element, return "line-through".
  1293. if (command == CommandNames::strikethrough && (element->local_name() == HTML::TagNames::s || element->local_name() == HTML::TagNames::strike))
  1294. return "line-through"_string;
  1295. // FIXME: 6. If command is "underline", and element has a style attribute set, and that attribute sets "text-decoration":
  1296. if (false) {
  1297. // FIXME: 1. If element's style attribute sets "text-decoration" to a value containing "underline", return "underline".
  1298. // 2. Return null.
  1299. return {};
  1300. }
  1301. // 7. If command is "underline" and element is a u element, return "underline".
  1302. if (command == CommandNames::underline && element->local_name() == HTML::TagNames::u)
  1303. return "underline"_string;
  1304. // FIXME: 8. Let property be the relevant CSS property for command.
  1305. // FIXME: 9. If property is null, return null.
  1306. // FIXME: 10. If element has a style attribute set, and that attribute has the effect of setting property, return the value
  1307. // that it sets property to.
  1308. // FIXME: 11. If element is a font element that has an attribute whose effect is to create a presentational hint for
  1309. // property, return the value that the hint sets property to. (For a size of 7, this will be the non-CSS value
  1310. // "xxx-large".)
  1311. // FIXME: 12. If element is in the following list, and property is equal to the CSS property name listed for it, return the
  1312. // string listed for it.
  1313. // * b, strong: font-weight: "bold"
  1314. // * i, em: font-style: "italic"
  1315. // 13. Return null.
  1316. return {};
  1317. }
  1318. // https://w3c.github.io/editing/docs/execCommand/#split-the-parent
  1319. void split_the_parent_of_nodes(Vector<GC::Ref<DOM::Node>> const& nodes)
  1320. {
  1321. VERIFY(nodes.size() > 0);
  1322. // 1. Let original parent be the parent of the first member of node list.
  1323. GC::Ref<DOM::Node> first_node = *nodes.first();
  1324. GC::Ref<DOM::Node> last_node = *nodes.last();
  1325. GC::Ref<DOM::Node> original_parent = *first_node->parent();
  1326. // 2. If original parent is not editable or its parent is null, do nothing and abort these
  1327. // steps.
  1328. if (!original_parent->is_editable() || !original_parent->parent())
  1329. return;
  1330. // 3. If the first child of original parent is in node list, remove extraneous line breaks
  1331. // before original parent.
  1332. GC::Ref<DOM::Node> first_child = *original_parent->first_child();
  1333. auto first_child_in_nodes_list = nodes.contains_slow(first_child);
  1334. if (first_child_in_nodes_list)
  1335. remove_extraneous_line_breaks_before_node(original_parent);
  1336. // 4. If the first child of original parent is in node list, and original parent follows a line
  1337. // break, set follows line break to true. Otherwise, set follows line break to false.
  1338. auto follows_line_break = first_child_in_nodes_list && follows_a_line_break(original_parent);
  1339. // 5. If the last child of original parent is in node list, and original parent precedes a line
  1340. // break, set precedes line break to true. Otherwise, set precedes line break to false.
  1341. GC::Ref<DOM::Node> last_child = *original_parent->last_child();
  1342. bool last_child_in_nodes_list = nodes.contains_slow(last_child);
  1343. auto precedes_line_break = last_child_in_nodes_list && precedes_a_line_break(original_parent);
  1344. // 6. If the first child of original parent is not in node list, but its last child is:
  1345. GC::Ref<DOM::Node> parent_of_original_parent = *original_parent->parent();
  1346. auto original_parent_index = original_parent->index();
  1347. auto& document = original_parent->document();
  1348. if (!first_child_in_nodes_list && last_child_in_nodes_list) {
  1349. // 1. For each node in node list, in reverse order, insert node into the parent of original
  1350. // parent immediately after original parent, preserving ranges.
  1351. for (auto node : nodes.in_reverse())
  1352. move_node_preserving_ranges(node, parent_of_original_parent, original_parent_index + 1);
  1353. // 2. If precedes line break is true, and the last member of node list does not precede a
  1354. // line break, call createElement("br") on the context object and insert the result
  1355. // immediately after the last member of node list.
  1356. if (precedes_line_break && !precedes_a_line_break(last_node)) {
  1357. auto br_element = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML));
  1358. MUST(last_node->parent()->append_child(br_element));
  1359. }
  1360. // 3. Remove extraneous line breaks at the end of original parent.
  1361. remove_extraneous_line_breaks_at_the_end_of_node(original_parent);
  1362. // 4. Abort these steps.
  1363. return;
  1364. }
  1365. // 7. If the first child of original parent is not in node list:
  1366. if (!first_child_in_nodes_list) {
  1367. // 1. Let cloned parent be the result of calling cloneNode(false) on original parent.
  1368. auto cloned_parent = MUST(original_parent->clone_node(nullptr, false));
  1369. // 2. If original parent has an id attribute, unset it.
  1370. auto& original_parent_element = static_cast<DOM::Element&>(*original_parent);
  1371. if (original_parent_element.has_attribute(HTML::AttributeNames::id))
  1372. original_parent_element.remove_attribute(HTML::AttributeNames::id);
  1373. // 3. Insert cloned parent into the parent of original parent immediately before original
  1374. // parent.
  1375. original_parent->parent()->insert_before(cloned_parent, original_parent);
  1376. // 4. While the previousSibling of the first member of node list is not null, append the
  1377. // first child of original parent as the last child of cloned parent, preserving ranges.
  1378. while (first_node->previous_sibling())
  1379. move_node_preserving_ranges(*original_parent->first_child(), cloned_parent, cloned_parent->child_count());
  1380. }
  1381. // 8. For each node in node list, insert node into the parent of original parent immediately
  1382. // before original parent, preserving ranges.
  1383. for (auto node : nodes)
  1384. move_node_preserving_ranges(node, parent_of_original_parent, original_parent_index - 1);
  1385. // 9. If follows line break is true, and the first member of node list does not follow a line
  1386. // break, call createElement("br") on the context object and insert the result immediately
  1387. // before the first member of node list.
  1388. if (follows_line_break && !follows_a_line_break(first_node)) {
  1389. auto br_element = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML));
  1390. first_node->parent()->insert_before(br_element, first_node);
  1391. }
  1392. // 10. If the last member of node list is an inline node other than a br, and the first child of
  1393. // original parent is a br, and original parent is not an inline node, remove the first
  1394. // child of original parent from original parent.
  1395. if (is_inline_node(last_node) && !is<HTML::HTMLBRElement>(*last_node) && is<HTML::HTMLBRElement>(*first_child) && !is_inline_node(original_parent))
  1396. first_child->remove();
  1397. // 11. If original parent has no children:
  1398. if (original_parent->child_count() == 0) {
  1399. // 1. Remove original parent from its parent.
  1400. original_parent->remove();
  1401. // 2. If precedes line break is true, and the last member of node list does not precede a
  1402. // line break, call createElement("br") on the context object and insert the result
  1403. // immediately after the last member of node list.
  1404. if (precedes_line_break && !precedes_a_line_break(last_node)) {
  1405. auto br_element = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML));
  1406. last_node->parent()->insert_before(br_element, last_node->next_sibling());
  1407. }
  1408. }
  1409. // 12. Otherwise, remove extraneous line breaks before original parent.
  1410. else {
  1411. remove_extraneous_line_breaks_before_node(original_parent);
  1412. }
  1413. // 13. If node list's last member's nextSibling is null, but its parent is not null, remove
  1414. // extraneous line breaks at the end of node list's last member's parent.
  1415. if (!last_node->next_sibling() && last_node->parent())
  1416. remove_extraneous_line_breaks_at_the_end_of_node(*last_node->parent());
  1417. }
  1418. // https://w3c.github.io/editing/docs/execCommand/#wrap
  1419. GC::Ptr<DOM::Node> wrap(
  1420. Vector<GC::Ref<DOM::Node>> node_list,
  1421. Function<bool(GC::Ref<DOM::Node>)> sibling_criteria,
  1422. Function<GC::Ptr<DOM::Node>()> new_parent_instructions)
  1423. {
  1424. VERIFY(!node_list.is_empty());
  1425. // If not provided, sibling criteria returns false and new parent instructions returns null.
  1426. if (!sibling_criteria)
  1427. sibling_criteria = [](auto) { return false; };
  1428. if (!new_parent_instructions)
  1429. new_parent_instructions = [] { return nullptr; };
  1430. // 1. If every member of node list is invisible, and none is a br, return null and abort these steps.
  1431. auto any_node_visible_or_br = false;
  1432. for (auto& node : node_list) {
  1433. if (is_visible_node(node) || is<HTML::HTMLBRElement>(*node)) {
  1434. any_node_visible_or_br = true;
  1435. break;
  1436. }
  1437. }
  1438. if (!any_node_visible_or_br)
  1439. return {};
  1440. // 2. If node list's first member's parent is null, return null and abort these steps.
  1441. if (!node_list.first()->parent())
  1442. return {};
  1443. // 3. If node list's last member is an inline node that's not a br, and node list's last member's nextSibling is a
  1444. // br, append that br to node list.
  1445. auto last_member = node_list.last();
  1446. if (is_inline_node(last_member) && !is<HTML::HTMLBRElement>(*last_member) && is<HTML::HTMLBRElement>(last_member->next_sibling()))
  1447. node_list.append(*last_member->next_sibling());
  1448. // 4. While node list's first member's previousSibling is invisible, prepend it to node list.
  1449. while (node_list.first()->previous_sibling() && is_invisible_node(*node_list.first()->previous_sibling()))
  1450. node_list.prepend(*node_list.first()->previous_sibling());
  1451. // 5. While node list's last member's nextSibling is invisible, append it to node list.
  1452. while (node_list.last()->next_sibling() && is_invisible_node(*node_list.last()->next_sibling()))
  1453. node_list.append(*node_list.last()->next_sibling());
  1454. auto new_parent = [&]() -> GC::Ptr<DOM::Node> {
  1455. // 6. If the previousSibling of the first member of node list is editable and running sibling criteria on it returns
  1456. // true, let new parent be the previousSibling of the first member of node list.
  1457. GC::Ptr<DOM::Node> previous_sibling = node_list.first()->previous_sibling();
  1458. if (previous_sibling && previous_sibling->is_editable() && sibling_criteria(*previous_sibling))
  1459. return previous_sibling;
  1460. // 7. Otherwise, if the nextSibling of the last member of node list is editable and running sibling criteria on it
  1461. // returns true, let new parent be the nextSibling of the last member of node list.
  1462. GC::Ptr<DOM::Node> next_sibling = node_list.last()->next_sibling();
  1463. if (next_sibling && next_sibling->is_editable() && sibling_criteria(*next_sibling))
  1464. return next_sibling;
  1465. // 8. Otherwise, run new parent instructions, and let new parent be the result.
  1466. return new_parent_instructions();
  1467. }();
  1468. // 9. If new parent is null, abort these steps and return null.
  1469. if (!new_parent)
  1470. return {};
  1471. // 10. If new parent's parent is null:
  1472. if (!new_parent->parent()) {
  1473. // 1. Insert new parent into the parent of the first member of node list immediately before the first member of
  1474. // node list.
  1475. auto first_member = node_list.first();
  1476. first_member->parent()->insert_before(*new_parent, first_member);
  1477. // FIXME: 2. If any range has a boundary point with node equal to the parent of new parent and offset equal to the
  1478. // index of new parent, add one to that boundary point's offset.
  1479. }
  1480. // 11. Let original parent be the parent of the first member of node list.
  1481. auto const original_parent = GC::Ptr { node_list.first()->parent() };
  1482. // 12. If new parent is before the first member of node list in tree order:
  1483. if (new_parent->is_before(node_list.first())) {
  1484. // 1. If new parent is not an inline node, but the last visible child of new parent and the first visible member
  1485. // of node list are both inline nodes, and the last child of new parent is not a br, call createElement("br")
  1486. // on the ownerDocument of new parent and append the result as the last child of new parent.
  1487. if (!is_inline_node(*new_parent)) {
  1488. auto last_visible_child = [&] -> GC::Ref<DOM::Node> {
  1489. GC::Ptr<DOM::Node> child = new_parent->last_child();
  1490. do {
  1491. if (is_visible_node(*child))
  1492. return *child;
  1493. child = child->previous_sibling();
  1494. } while (child);
  1495. VERIFY_NOT_REACHED();
  1496. }();
  1497. auto first_visible_member = [&] -> GC::Ref<DOM::Node> {
  1498. for (auto& member : node_list) {
  1499. if (is_visible_node(member))
  1500. return member;
  1501. }
  1502. VERIFY_NOT_REACHED();
  1503. }();
  1504. if (is_inline_node(last_visible_child) && is_inline_node(first_visible_member)
  1505. && !is<HTML::HTMLBRElement>(new_parent->last_child())) {
  1506. auto br_element = MUST(DOM::create_element(*new_parent->owner_document(), HTML::TagNames::br, Namespace::HTML));
  1507. MUST(new_parent->append_child(br_element));
  1508. }
  1509. }
  1510. // 2. For each node in node list, append node as the last child of new parent, preserving ranges.
  1511. auto new_position = new_parent->child_count();
  1512. for (auto& node : node_list)
  1513. move_node_preserving_ranges(node, *new_parent, new_position++);
  1514. }
  1515. // 13. Otherwise:
  1516. else {
  1517. // 1. If new parent is not an inline node, but the first visible child of new parent and the last visible member
  1518. // of node list are both inline nodes, and the last member of node list is not a br, call createElement("br")
  1519. // on the ownerDocument of new parent and insert the result as the first child of new parent.
  1520. if (!is_inline_node(*new_parent)) {
  1521. auto first_visible_child = [&] -> GC::Ref<DOM::Node> {
  1522. GC::Ptr<DOM::Node> child = new_parent->first_child();
  1523. do {
  1524. if (is_visible_node(*child))
  1525. return *child;
  1526. child = child->next_sibling();
  1527. } while (child);
  1528. VERIFY_NOT_REACHED();
  1529. }();
  1530. auto last_visible_member = [&] -> GC::Ref<DOM::Node> {
  1531. for (auto& member : node_list.in_reverse()) {
  1532. if (is_visible_node(member))
  1533. return member;
  1534. }
  1535. VERIFY_NOT_REACHED();
  1536. }();
  1537. if (is_inline_node(first_visible_child) && is_inline_node(last_visible_member)
  1538. && !is<HTML::HTMLBRElement>(*node_list.last())) {
  1539. auto br_element = MUST(DOM::create_element(*new_parent->owner_document(), HTML::TagNames::br, Namespace::HTML));
  1540. new_parent->insert_before(br_element, new_parent->first_child());
  1541. }
  1542. }
  1543. // 2. For each node in node list, in reverse order, insert node as the first child of new parent, preserving
  1544. // ranges.
  1545. for (auto& node : node_list.in_reverse())
  1546. move_node_preserving_ranges(node, *new_parent, 0);
  1547. }
  1548. // 14. If original parent is editable and has no children, remove it from its parent.
  1549. if (original_parent->is_editable() && !original_parent->has_children())
  1550. original_parent->remove();
  1551. // 15. If new parent's nextSibling is editable and running sibling criteria on it returns true:
  1552. GC::Ptr<DOM::Node> next_sibling = new_parent->next_sibling();
  1553. if (next_sibling && next_sibling->is_editable() && sibling_criteria(*next_sibling)) {
  1554. // 1. If new parent is not an inline node, but new parent's last child and new parent's nextSibling's first
  1555. // child are both inline nodes, and new parent's last child is not a br, call createElement("br") on the
  1556. // ownerDocument of new parent and append the result as the last child of new parent.
  1557. if (!is_inline_node(*new_parent) && is_inline_node(*new_parent->last_child())
  1558. && is_inline_node(*next_sibling->first_child()) && !is<HTML::HTMLBRElement>(new_parent->last_child())) {
  1559. auto br_element = MUST(DOM::create_element(*new_parent->owner_document(), HTML::TagNames::br, Namespace::HTML));
  1560. MUST(new_parent->append_child(br_element));
  1561. }
  1562. // 2. While new parent's nextSibling has children, append its first child as the last child of new parent,
  1563. // preserving ranges.
  1564. auto new_position = new_parent->child_count();
  1565. while (next_sibling->has_children())
  1566. move_node_preserving_ranges(*next_sibling->first_child(), *new_parent, new_position++);
  1567. // 3. Remove new parent's nextSibling from its parent.
  1568. next_sibling->remove();
  1569. }
  1570. // 16. Remove extraneous line breaks from new parent.
  1571. remove_extraneous_line_breaks_from_a_node(*new_parent);
  1572. // 17. Return new parent.
  1573. return new_parent;
  1574. }
  1575. bool is_heading(FlyString const& local_name)
  1576. {
  1577. return local_name.is_one_of(
  1578. HTML::TagNames::h1,
  1579. HTML::TagNames::h2,
  1580. HTML::TagNames::h3,
  1581. HTML::TagNames::h4,
  1582. HTML::TagNames::h5,
  1583. HTML::TagNames::h6);
  1584. }
  1585. }