Selection.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. /*
  2. * Copyright (c) 2021-2022, Andreas Kling <andreas@ladybird.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibUnicode/Segmenter.h>
  7. #include <LibWeb/Bindings/Intrinsics.h>
  8. #include <LibWeb/Bindings/SelectionPrototype.h>
  9. #include <LibWeb/DOM/Document.h>
  10. #include <LibWeb/DOM/Position.h>
  11. #include <LibWeb/DOM/Range.h>
  12. #include <LibWeb/DOM/Text.h>
  13. #include <LibWeb/Selection/Selection.h>
  14. namespace Web::Selection {
  15. GC_DEFINE_ALLOCATOR(Selection);
  16. GC::Ref<Selection> Selection::create(GC::Ref<JS::Realm> realm, GC::Ref<DOM::Document> document)
  17. {
  18. return realm->create<Selection>(realm, document);
  19. }
  20. Selection::Selection(GC::Ref<JS::Realm> realm, GC::Ref<DOM::Document> document)
  21. : PlatformObject(realm)
  22. , m_document(document)
  23. {
  24. }
  25. Selection::~Selection() = default;
  26. void Selection::initialize(JS::Realm& realm)
  27. {
  28. Base::initialize(realm);
  29. WEB_SET_PROTOTYPE_FOR_INTERFACE(Selection);
  30. }
  31. // https://w3c.github.io/selection-api/#dfn-empty
  32. bool Selection::is_empty() const
  33. {
  34. // Each selection can be associated with a single range.
  35. // When there is no range associated with the selection, the selection is empty.
  36. // The selection must be initially empty.
  37. // NOTE: This function should not be confused with Selection.empty() which empties the selection.
  38. return !m_range;
  39. }
  40. void Selection::visit_edges(Cell::Visitor& visitor)
  41. {
  42. Base::visit_edges(visitor);
  43. visitor.visit(m_range);
  44. visitor.visit(m_document);
  45. }
  46. // https://w3c.github.io/selection-api/#dfn-anchor
  47. GC::Ptr<DOM::Node> Selection::anchor_node()
  48. {
  49. if (!m_range)
  50. return nullptr;
  51. if (m_direction == Direction::Forwards)
  52. return m_range->start_container();
  53. return m_range->end_container();
  54. }
  55. // https://w3c.github.io/selection-api/#dfn-anchor
  56. unsigned Selection::anchor_offset()
  57. {
  58. if (!m_range)
  59. return 0;
  60. if (m_direction == Direction::Forwards)
  61. return m_range->start_offset();
  62. return m_range->end_offset();
  63. }
  64. // https://w3c.github.io/selection-api/#dfn-focus
  65. GC::Ptr<DOM::Node> Selection::focus_node()
  66. {
  67. if (!m_range)
  68. return nullptr;
  69. if (m_direction == Direction::Forwards)
  70. return m_range->end_container();
  71. return m_range->start_container();
  72. }
  73. // https://w3c.github.io/selection-api/#dfn-focus
  74. unsigned Selection::focus_offset() const
  75. {
  76. if (!m_range)
  77. return 0;
  78. if (m_direction == Direction::Forwards)
  79. return m_range->end_offset();
  80. return m_range->start_offset();
  81. }
  82. // https://w3c.github.io/selection-api/#dom-selection-iscollapsed
  83. bool Selection::is_collapsed() const
  84. {
  85. // The attribute must return true if and only if the anchor and focus are the same
  86. // (including if both are null). Otherwise it must return false.
  87. if (!m_range)
  88. return true;
  89. return const_cast<Selection*>(this)->anchor_node() == const_cast<Selection*>(this)->focus_node()
  90. && m_range->start_offset() == m_range->end_offset();
  91. }
  92. // https://w3c.github.io/selection-api/#dom-selection-rangecount
  93. unsigned Selection::range_count() const
  94. {
  95. if (m_range)
  96. return 1;
  97. return 0;
  98. }
  99. String Selection::type() const
  100. {
  101. if (!m_range)
  102. return "None"_string;
  103. if (m_range->collapsed())
  104. return "Caret"_string;
  105. return "Range"_string;
  106. }
  107. String Selection::direction() const
  108. {
  109. if (!m_range || m_direction == Direction::Directionless)
  110. return "none"_string;
  111. if (m_direction == Direction::Forwards)
  112. return "forward"_string;
  113. return "backward"_string;
  114. }
  115. // https://w3c.github.io/selection-api/#dom-selection-getrangeat
  116. WebIDL::ExceptionOr<GC::Ptr<DOM::Range>> Selection::get_range_at(unsigned index)
  117. {
  118. // The method must throw an IndexSizeError exception if index is not 0, or if this is empty.
  119. if (index != 0 || is_empty())
  120. return WebIDL::IndexSizeError::create(realm(), "Selection.getRangeAt() on empty Selection or with invalid argument"_string);
  121. // Otherwise, it must return a reference to (not a copy of) this's range.
  122. return m_range;
  123. }
  124. // https://w3c.github.io/selection-api/#dom-selection-addrange
  125. void Selection::add_range(GC::Ref<DOM::Range> range)
  126. {
  127. // 1. If the root of the range's boundary points are not the document associated with this, abort these steps.
  128. if (&range->start_container()->root() != m_document.ptr())
  129. return;
  130. // 2. If rangeCount is not 0, abort these steps.
  131. if (range_count() != 0)
  132. return;
  133. // 3. Set this's range to range by a strong reference (not by making a copy).
  134. set_range(range);
  135. // AD-HOC: WPT selection/removeAllRanges.html and selection/addRange.htm expect this
  136. m_direction = Direction::Forwards;
  137. }
  138. // https://w3c.github.io/selection-api/#dom-selection-removerange
  139. WebIDL::ExceptionOr<void> Selection::remove_range(GC::Ref<DOM::Range> range)
  140. {
  141. // The method must make this empty by disassociating its range if this's range is range.
  142. if (m_range == range) {
  143. set_range(nullptr);
  144. return {};
  145. }
  146. // Otherwise, it must throw a NotFoundError.
  147. return WebIDL::NotFoundError::create(realm(), "Selection.removeRange() with invalid argument"_string);
  148. }
  149. // https://w3c.github.io/selection-api/#dom-selection-removeallranges
  150. void Selection::remove_all_ranges()
  151. {
  152. // The method must make this empty by disassociating its range if this has an associated range.
  153. set_range(nullptr);
  154. }
  155. // https://w3c.github.io/selection-api/#dom-selection-empty
  156. void Selection::empty()
  157. {
  158. // The method must be an alias, and behave identically, to removeAllRanges().
  159. remove_all_ranges();
  160. }
  161. // https://w3c.github.io/selection-api/#dom-selection-collapse
  162. WebIDL::ExceptionOr<void> Selection::collapse(GC::Ptr<DOM::Node> node, unsigned offset)
  163. {
  164. // 1. If node is null, this method must behave identically as removeAllRanges() and abort these steps.
  165. if (!node) {
  166. remove_all_ranges();
  167. return {};
  168. }
  169. // FIXME: Update this to match the spec once the spec is updated.
  170. // Spec PR: https://github.com/w3c/selection-api/pull/342
  171. if (node->is_document_type()) {
  172. return WebIDL::InvalidNodeTypeError::create(realm(), "Selection.collapse() with DocumentType node"_string);
  173. }
  174. // 2. The method must throw an IndexSizeError exception if offset is longer than node's length and abort these steps.
  175. if (offset > node->length()) {
  176. return WebIDL::IndexSizeError::create(realm(), "Selection.collapse() with offset longer than node's length"_string);
  177. }
  178. // 3. If document associated with this is not a shadow-including inclusive ancestor of node, abort these steps.
  179. if (!m_document->is_shadow_including_inclusive_ancestor_of(*node))
  180. return {};
  181. // 4. Otherwise, let newRange be a new range.
  182. auto new_range = DOM::Range::create(*m_document);
  183. // 5. Set the start the start and the end of newRange to (node, offset).
  184. TRY(new_range->set_start(*node, offset));
  185. // 6. Set this's range to newRange.
  186. set_range(new_range);
  187. return {};
  188. }
  189. // https://w3c.github.io/selection-api/#dom-selection-setposition
  190. WebIDL::ExceptionOr<void> Selection::set_position(GC::Ptr<DOM::Node> node, unsigned offset)
  191. {
  192. // The method must be an alias, and behave identically, to collapse().
  193. return collapse(node, offset);
  194. }
  195. // https://w3c.github.io/selection-api/#dom-selection-collapsetostart
  196. WebIDL::ExceptionOr<void> Selection::collapse_to_start()
  197. {
  198. // 1. The method must throw InvalidStateError exception if the this is empty.
  199. if (!m_range) {
  200. return WebIDL::InvalidStateError::create(realm(), "Selection.collapse_to_start() on empty range"_string);
  201. }
  202. // 2. Otherwise, it must create a new range
  203. auto new_range = DOM::Range::create(*m_document);
  204. // 3. Set the start both its start and end to the start of this's range
  205. TRY(new_range->set_start(*anchor_node(), m_range->start_offset()));
  206. TRY(new_range->set_end(*anchor_node(), m_range->start_offset()));
  207. // 4. Then set this's range to the newly-created range.
  208. set_range(new_range);
  209. return {};
  210. }
  211. // https://w3c.github.io/selection-api/#dom-selection-collapsetoend
  212. WebIDL::ExceptionOr<void> Selection::collapse_to_end()
  213. {
  214. // 1. The method must throw InvalidStateError exception if the this is empty.
  215. if (!m_range) {
  216. return WebIDL::InvalidStateError::create(realm(), "Selection.collapse_to_end() on empty range"_string);
  217. }
  218. // 2. Otherwise, it must create a new range
  219. auto new_range = DOM::Range::create(*m_document);
  220. // 3. Set the start both its start and end to the start of this's range
  221. TRY(new_range->set_start(*anchor_node(), m_range->end_offset()));
  222. TRY(new_range->set_end(*anchor_node(), m_range->end_offset()));
  223. // 4. Then set this's range to the newly-created range.
  224. set_range(new_range);
  225. return {};
  226. }
  227. // https://w3c.github.io/selection-api/#dom-selection-extend
  228. WebIDL::ExceptionOr<void> Selection::extend(GC::Ref<DOM::Node> node, unsigned offset)
  229. {
  230. // 1. If the document associated with this is not a shadow-including inclusive ancestor of node, abort these steps.
  231. if (!m_document->is_shadow_including_inclusive_ancestor_of(node))
  232. return {};
  233. // 2. If this is empty, throw an InvalidStateError exception and abort these steps.
  234. if (!m_range) {
  235. return WebIDL::InvalidStateError::create(realm(), "Selection.extend() on empty range"_string);
  236. }
  237. // 3. Let oldAnchor and oldFocus be the this's anchor and focus, and let newFocus be the boundary point (node, offset).
  238. auto& old_anchor_node = *anchor_node();
  239. auto old_anchor_offset = anchor_offset();
  240. auto& new_focus_node = node;
  241. auto new_focus_offset = offset;
  242. // 4. Let newRange be a new range.
  243. auto new_range = DOM::Range::create(*m_document);
  244. // 5. If node's root is not the same as the this's range's root, set the start newRange's start and end to newFocus.
  245. if (&node->root() != &m_range->start_container()->root()) {
  246. TRY(new_range->set_start(new_focus_node, new_focus_offset));
  247. TRY(new_range->set_end(new_focus_node, new_focus_offset));
  248. }
  249. // 6. Otherwise, if oldAnchor is before or equal to newFocus, set the start newRange's start to oldAnchor, then set its end to newFocus.
  250. else if (position_of_boundary_point_relative_to_other_boundary_point(old_anchor_node, old_anchor_offset, new_focus_node, new_focus_offset) != DOM::RelativeBoundaryPointPosition::After) {
  251. TRY(new_range->set_start(old_anchor_node, old_anchor_offset));
  252. TRY(new_range->set_end(new_focus_node, new_focus_offset));
  253. }
  254. // 7. Otherwise, set the start newRange's start to newFocus, then set its end to oldAnchor.
  255. else {
  256. TRY(new_range->set_start(new_focus_node, new_focus_offset));
  257. TRY(new_range->set_end(old_anchor_node, old_anchor_offset));
  258. }
  259. // 8. Set this's range to newRange.
  260. set_range(new_range);
  261. // 9. If newFocus is before oldAnchor, set this's direction to backwards. Otherwise, set it to forwards.
  262. if (position_of_boundary_point_relative_to_other_boundary_point(new_focus_node, new_focus_offset, old_anchor_node, old_anchor_offset) == DOM::RelativeBoundaryPointPosition::Before) {
  263. m_direction = Direction::Backwards;
  264. } else {
  265. m_direction = Direction::Forwards;
  266. }
  267. return {};
  268. }
  269. // https://w3c.github.io/selection-api/#dom-selection-setbaseandextent
  270. WebIDL::ExceptionOr<void> Selection::set_base_and_extent(GC::Ref<DOM::Node> anchor_node, unsigned anchor_offset, GC::Ref<DOM::Node> focus_node, unsigned focus_offset)
  271. {
  272. // 1. If anchorOffset is longer than anchorNode's length or if focusOffset is longer than focusNode's length, throw an IndexSizeError exception and abort these steps.
  273. if (anchor_offset > anchor_node->length())
  274. return WebIDL::IndexSizeError::create(realm(), "Anchor offset points outside of the anchor node"_string);
  275. if (focus_offset > focus_node->length())
  276. return WebIDL::IndexSizeError::create(realm(), "Focus offset points outside of the focus node"_string);
  277. // 2. If document associated with this is not a shadow-including inclusive ancestor of anchorNode or focusNode, abort these steps.
  278. if (!m_document->is_shadow_including_inclusive_ancestor_of(anchor_node) || !m_document->is_shadow_including_inclusive_ancestor_of(focus_node))
  279. return {};
  280. // 3. Let anchor be the boundary point (anchorNode, anchorOffset) and let focus be the boundary point (focusNode, focusOffset).
  281. // 4. Let newRange be a new range.
  282. auto new_range = DOM::Range::create(*m_document);
  283. // 5. If anchor is before focus, set the start the newRange's start to anchor and its end to focus. Otherwise, set the start them to focus and anchor respectively.
  284. auto position_of_anchor_relative_to_focus = DOM::position_of_boundary_point_relative_to_other_boundary_point(anchor_node, anchor_offset, focus_node, focus_offset);
  285. if (position_of_anchor_relative_to_focus == DOM::RelativeBoundaryPointPosition::Before) {
  286. TRY(new_range->set_start(anchor_node, anchor_offset));
  287. TRY(new_range->set_end(focus_node, focus_offset));
  288. } else {
  289. TRY(new_range->set_start(focus_node, focus_offset));
  290. TRY(new_range->set_end(anchor_node, anchor_offset));
  291. }
  292. // 6. Set this's range to newRange.
  293. set_range(new_range);
  294. // 7. If focus is before anchor, set this's direction to backwards. Otherwise, set it to forwards
  295. // NOTE: "Otherwise" can be seen as "focus is equal to or after anchor".
  296. if (position_of_anchor_relative_to_focus == DOM::RelativeBoundaryPointPosition::After)
  297. m_direction = Direction::Backwards;
  298. else
  299. m_direction = Direction::Forwards;
  300. return {};
  301. }
  302. // https://w3c.github.io/selection-api/#dom-selection-selectallchildren
  303. WebIDL::ExceptionOr<void> Selection::select_all_children(GC::Ref<DOM::Node> node)
  304. {
  305. // FIXME: Update this to match the spec once the spec is updated.
  306. // Spec PR: https://github.com/w3c/selection-api/pull/342
  307. if (node->is_document_type()) {
  308. return WebIDL::InvalidNodeTypeError::create(realm(), "Selection.selectAllChildren() with DocumentType node"_string);
  309. }
  310. // 1. If node's root is not the document associated with this, abort these steps.
  311. if (&node->root() != m_document.ptr())
  312. return {};
  313. // 2. Let newRange be a new range and childCount be the number of children of node.
  314. auto new_range = DOM::Range::create(*m_document);
  315. auto child_count = node->child_count();
  316. // 3. Set newRange's start to (node, 0).
  317. TRY(new_range->set_start(node, 0));
  318. // 4. Set newRange's end to (node, childCount).
  319. TRY(new_range->set_end(node, child_count));
  320. // 5. Set this's range to newRange.
  321. set_range(new_range);
  322. // 6. Set this's direction to forwards.
  323. m_direction = Direction::Forwards;
  324. return {};
  325. }
  326. // https://w3c.github.io/selection-api/#dom-selection-deletefromdocument
  327. WebIDL::ExceptionOr<void> Selection::delete_from_document()
  328. {
  329. // The method must invoke deleteContents() on this's range if this is not empty.
  330. // Otherwise the method must do nothing.
  331. if (!is_empty())
  332. return m_range->delete_contents();
  333. return {};
  334. }
  335. // https://w3c.github.io/selection-api/#dom-selection-containsnode
  336. bool Selection::contains_node(GC::Ref<DOM::Node> node, bool allow_partial_containment) const
  337. {
  338. // The method must return false if this is empty or if node's root is not the document associated with this.
  339. if (!m_range)
  340. return false;
  341. if (&node->root() != m_document.ptr())
  342. return false;
  343. // Otherwise, if allowPartialContainment is false, the method must return true if and only if
  344. // start of its range is before or visually equivalent to the first boundary point in the node
  345. // and end of its range is after or visually equivalent to the last boundary point in the node.
  346. if (!allow_partial_containment) {
  347. auto start_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(
  348. *m_range->start_container(),
  349. m_range->start_offset(),
  350. node,
  351. 0);
  352. auto end_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(
  353. *m_range->end_container(),
  354. m_range->end_offset(),
  355. node,
  356. node->length());
  357. return (start_relative_position == DOM::RelativeBoundaryPointPosition::Before || start_relative_position == DOM::RelativeBoundaryPointPosition::Equal)
  358. && (end_relative_position == DOM::RelativeBoundaryPointPosition::Equal || end_relative_position == DOM::RelativeBoundaryPointPosition::After);
  359. }
  360. // If allowPartialContainment is true, the method must return true if and only if
  361. // start of its range is before or visually equivalent to the last boundary point in the node
  362. // and end of its range is after or visually equivalent to the first boundary point in the node.
  363. auto start_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(
  364. *m_range->start_container(),
  365. m_range->start_offset(),
  366. node,
  367. node->length());
  368. auto end_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(
  369. *m_range->end_container(),
  370. m_range->end_offset(),
  371. node,
  372. 0);
  373. return (start_relative_position == DOM::RelativeBoundaryPointPosition::Before || start_relative_position == DOM::RelativeBoundaryPointPosition::Equal)
  374. && (end_relative_position == DOM::RelativeBoundaryPointPosition::Equal || end_relative_position == DOM::RelativeBoundaryPointPosition::After);
  375. }
  376. String Selection::to_string() const
  377. {
  378. // FIXME: This needs more work to be compatible with other engines.
  379. // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=10583
  380. if (!m_range)
  381. return String {};
  382. return m_range->to_string();
  383. }
  384. GC::Ref<DOM::Document> Selection::document() const
  385. {
  386. return m_document;
  387. }
  388. GC::Ptr<DOM::Range> Selection::range() const
  389. {
  390. return m_range;
  391. }
  392. void Selection::set_range(GC::Ptr<DOM::Range> range)
  393. {
  394. if (m_range == range)
  395. return;
  396. if (m_range)
  397. m_range->set_associated_selection({}, nullptr);
  398. m_range = range;
  399. if (m_range)
  400. m_range->set_associated_selection({}, this);
  401. }
  402. GC::Ptr<DOM::Position> Selection::cursor_position() const
  403. {
  404. if (!m_range)
  405. return nullptr;
  406. if (is_collapsed()) {
  407. return DOM::Position::create(m_document->realm(), *m_range->start_container(), m_range->start_offset());
  408. }
  409. return nullptr;
  410. }
  411. void Selection::move_offset_to_next_character(bool collapse_selection)
  412. {
  413. auto anchor_node = this->anchor_node();
  414. if (!anchor_node || !is<DOM::Text>(*anchor_node))
  415. return;
  416. auto& text_node = static_cast<DOM::Text&>(*anchor_node);
  417. if (auto offset = text_node.grapheme_segmenter().next_boundary(focus_offset()); offset.has_value()) {
  418. if (collapse_selection) {
  419. MUST(collapse(*anchor_node, *offset));
  420. m_document->reset_cursor_blink_cycle();
  421. } else {
  422. MUST(set_base_and_extent(*anchor_node, anchor_offset(), *anchor_node, *offset));
  423. }
  424. }
  425. }
  426. void Selection::move_offset_to_previous_character(bool collapse_selection)
  427. {
  428. auto anchor_node = this->anchor_node();
  429. if (!anchor_node || !is<DOM::Text>(*anchor_node))
  430. return;
  431. auto& text_node = static_cast<DOM::Text&>(*anchor_node);
  432. if (auto offset = text_node.grapheme_segmenter().previous_boundary(focus_offset()); offset.has_value()) {
  433. if (collapse_selection) {
  434. MUST(collapse(*anchor_node, *offset));
  435. m_document->reset_cursor_blink_cycle();
  436. } else {
  437. MUST(set_base_and_extent(*anchor_node, anchor_offset(), *anchor_node, *offset));
  438. }
  439. }
  440. }
  441. void Selection::move_offset_to_next_word(bool collapse_selection)
  442. {
  443. auto anchor_node = this->anchor_node();
  444. if (!anchor_node || !is<DOM::Text>(*anchor_node)) {
  445. return;
  446. }
  447. auto& text_node = static_cast<DOM::Text&>(*anchor_node);
  448. while (true) {
  449. auto focus_offset = this->focus_offset();
  450. if (focus_offset == text_node.data().bytes_as_string_view().length()) {
  451. return;
  452. }
  453. if (auto offset = text_node.word_segmenter().next_boundary(focus_offset); offset.has_value()) {
  454. auto word = text_node.data().code_points().substring_view(focus_offset, *offset - focus_offset);
  455. if (collapse_selection) {
  456. MUST(collapse(anchor_node, *offset));
  457. m_document->reset_cursor_blink_cycle();
  458. } else {
  459. MUST(set_base_and_extent(*anchor_node, this->anchor_offset(), *anchor_node, *offset));
  460. }
  461. if (Unicode::Segmenter::should_continue_beyond_word(word))
  462. continue;
  463. }
  464. break;
  465. }
  466. }
  467. void Selection::move_offset_to_previous_word(bool collapse_selection)
  468. {
  469. auto anchor_node = this->anchor_node();
  470. if (!anchor_node || !is<DOM::Text>(*anchor_node)) {
  471. return;
  472. }
  473. auto& text_node = static_cast<DOM::Text&>(*anchor_node);
  474. while (true) {
  475. auto focus_offset = this->focus_offset();
  476. if (auto offset = text_node.word_segmenter().previous_boundary(focus_offset); offset.has_value()) {
  477. auto word = text_node.data().code_points().substring_view(focus_offset, focus_offset - *offset);
  478. if (collapse_selection) {
  479. MUST(collapse(anchor_node, *offset));
  480. m_document->reset_cursor_blink_cycle();
  481. } else {
  482. MUST(set_base_and_extent(*anchor_node, anchor_offset(), *anchor_node, *offset));
  483. }
  484. if (Unicode::Segmenter::should_continue_beyond_word(word))
  485. continue;
  486. }
  487. break;
  488. }
  489. }
  490. }