LibWeb: Add concept of boundary point to DOM::AbstractRange

This makes comparing the relative position of boundary points a bit
nicer when one of the boundary points is the range's start or end.
This commit is contained in:
Jelle Raaijmakers 2024-12-18 13:30:19 +01:00 committed by Jelle Raaijmakers
commit e6631a4216
Notes: github-actions[bot] 2024-12-21 18:17:22 +00:00
7 changed files with 88 additions and 102 deletions

View file

@ -13,6 +13,12 @@
namespace Web::DOM { namespace Web::DOM {
// https://dom.spec.whatwg.org/#concept-range-bp
struct BoundaryPoint {
GC::Ref<Node> node;
WebIDL::UnsignedLong offset;
};
// https://dom.spec.whatwg.org/#abstractrange // https://dom.spec.whatwg.org/#abstractrange
class AbstractRange : public Bindings::PlatformObject { class AbstractRange : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(AbstractRange, Bindings::PlatformObject); WEB_PLATFORM_OBJECT(AbstractRange, Bindings::PlatformObject);
@ -20,9 +26,11 @@ class AbstractRange : public Bindings::PlatformObject {
public: public:
virtual ~AbstractRange() override; virtual ~AbstractRange() override;
BoundaryPoint start() const { return { m_start_container, m_start_offset }; }
GC::Ref<Node> start_container() const { return m_start_container; } GC::Ref<Node> start_container() const { return m_start_container; }
WebIDL::UnsignedLong start_offset() const { return m_start_offset; } WebIDL::UnsignedLong start_offset() const { return m_start_offset; }
BoundaryPoint end() const { return { m_end_container, m_end_offset }; }
GC::Ref<Node> end_container() const { return m_end_container; } GC::Ref<Node> end_container() const { return m_end_container; }
WebIDL::UnsignedLong end_offset() const { return m_end_offset; } WebIDL::UnsignedLong end_offset() const { return m_end_offset; }

View file

@ -117,27 +117,27 @@ GC::Ref<Node> Range::root() const
} }
// https://dom.spec.whatwg.org/#concept-range-bp-position // https://dom.spec.whatwg.org/#concept-range-bp-position
RelativeBoundaryPointPosition position_of_boundary_point_relative_to_other_boundary_point(GC::Ref<Node> node_a, u32 offset_a, GC::Ref<Node> node_b, u32 offset_b) RelativeBoundaryPointPosition position_of_boundary_point_relative_to_other_boundary_point(BoundaryPoint a, BoundaryPoint b)
{ {
// 1. Assert: nodeA and nodeB have the same root. // 1. Assert: nodeA and nodeB have the same root.
// NOTE: Nodes may not share the same root if they belong to different shadow trees, // NOTE: Nodes may not share the same root if they belong to different shadow trees,
// so we assert that they share the same shadow-including root instead. // so we assert that they share the same shadow-including root instead.
VERIFY(&node_a->shadow_including_root() == &node_b->shadow_including_root()); VERIFY(&a.node->shadow_including_root() == &b.node->shadow_including_root());
// 2. If nodeA is nodeB, then return equal if offsetA is offsetB, before if offsetA is less than offsetB, and after if offsetA is greater than offsetB. // 2. If nodeA is nodeB, then return equal if offsetA is offsetB, before if offsetA is less than offsetB, and after if offsetA is greater than offsetB.
if (node_a == node_b) { if (a.node == b.node) {
if (offset_a == offset_b) if (a.offset == b.offset)
return RelativeBoundaryPointPosition::Equal; return RelativeBoundaryPointPosition::Equal;
if (offset_a < offset_b) if (a.offset < b.offset)
return RelativeBoundaryPointPosition::Before; return RelativeBoundaryPointPosition::Before;
return RelativeBoundaryPointPosition::After; return RelativeBoundaryPointPosition::After;
} }
// 3. If nodeA is following nodeB, then if the position of (nodeB, offsetB) relative to (nodeA, offsetA) is before, return after, and if it is after, return before. // 3. If nodeA is following nodeB, then if the position of (nodeB, offsetB) relative to (nodeA, offsetA) is before, return after, and if it is after, return before.
if (node_a->is_following(node_b)) { if (a.node->is_following(b.node)) {
auto relative_position = position_of_boundary_point_relative_to_other_boundary_point(node_b, offset_b, node_a, offset_a); auto relative_position = position_of_boundary_point_relative_to_other_boundary_point(b, a);
if (relative_position == RelativeBoundaryPointPosition::Before) if (relative_position == RelativeBoundaryPointPosition::Before)
return RelativeBoundaryPointPosition::After; return RelativeBoundaryPointPosition::After;
@ -147,19 +147,19 @@ RelativeBoundaryPointPosition position_of_boundary_point_relative_to_other_bound
} }
// 4. If nodeA is an ancestor of nodeB: // 4. If nodeA is an ancestor of nodeB:
if (node_a->is_ancestor_of(node_b)) { if (a.node->is_ancestor_of(b.node)) {
// 1. Let child be nodeB. // 1. Let child be nodeB.
GC::Ref<Node const> child = node_b; GC::Ref<Node const> child = b.node;
// 2. While child is not a child of nodeA, set child to its parent. // 2. While child is not a child of nodeA, set child to its parent.
while (!node_a->is_parent_of(child)) { while (!a.node->is_parent_of(child)) {
auto* parent = child->parent(); auto* parent = child->parent();
VERIFY(parent); VERIFY(parent);
child = *parent; child = *parent;
} }
// 3. If childs index is less than offsetA, then return after. // 3. If childs index is less than offsetA, then return after.
if (child->index() < offset_a) if (child->index() < a.offset)
return RelativeBoundaryPointPosition::After; return RelativeBoundaryPointPosition::After;
} }
@ -185,7 +185,7 @@ WebIDL::ExceptionOr<void> Range::set_start_or_end(GC::Ref<Node> node, u32 offset
// -> If these steps were invoked as "set the start" // -> If these steps were invoked as "set the start"
// 1. If ranges root is not equal to nodes root, or if bp is after the ranges end, set ranges end to bp. // 1. If ranges root is not equal to nodes root, or if bp is after the ranges end, set ranges end to bp.
if (root().ptr() != &node->root() || position_of_boundary_point_relative_to_other_boundary_point(node, offset, m_end_container, m_end_offset) == RelativeBoundaryPointPosition::After) { if (root().ptr() != &node->root() || position_of_boundary_point_relative_to_other_boundary_point({ node, offset }, end()) == RelativeBoundaryPointPosition::After) {
m_end_container = node; m_end_container = node;
m_end_offset = offset; m_end_offset = offset;
} }
@ -198,7 +198,7 @@ WebIDL::ExceptionOr<void> Range::set_start_or_end(GC::Ref<Node> node, u32 offset
VERIFY(start_or_end == StartOrEnd::End); VERIFY(start_or_end == StartOrEnd::End);
// 1. If ranges root is not equal to nodes root, or if bp is before the ranges start, set ranges start to bp. // 1. If ranges root is not equal to nodes root, or if bp is before the ranges start, set ranges start to bp.
if (root().ptr() != &node->root() || position_of_boundary_point_relative_to_other_boundary_point(node, offset, m_start_container, m_start_offset) == RelativeBoundaryPointPosition::Before) { if (root().ptr() != &node->root() || position_of_boundary_point_relative_to_other_boundary_point({ node, offset }, start()) == RelativeBoundaryPointPosition::Before) {
m_start_container = node; m_start_container = node;
m_start_offset = offset; m_start_offset = offset;
} }
@ -349,7 +349,7 @@ WebIDL::ExceptionOr<WebIDL::Short> Range::compare_boundary_points(WebIDL::Unsign
VERIFY(other_point_node); VERIFY(other_point_node);
// 4. If the position of this point relative to other point is // 4. If the position of this point relative to other point is
auto relative_position = position_of_boundary_point_relative_to_other_boundary_point(*this_point_node, this_point_offset, *other_point_node, other_point_offset); auto relative_position = position_of_boundary_point_relative_to_other_boundary_point({ *this_point_node, this_point_offset }, { *other_point_node, other_point_offset });
switch (relative_position) { switch (relative_position) {
case RelativeBoundaryPointPosition::Before: case RelativeBoundaryPointPosition::Before:
// -> before // -> before
@ -492,11 +492,11 @@ bool Range::intersects_node(GC::Ref<Node> node) const
return true; return true;
// 4. Let offset be nodes index. // 4. Let offset be nodes index.
auto offset = node->index(); WebIDL::UnsignedLong offset = node->index();
// 5. If (parent, offset) is before end and (parent, offset plus 1) is after start, return true // 5. If (parent, offset) is before end and (parent, offset plus 1) is after start, return true
auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point(*parent, offset, m_end_container, m_end_offset); auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point({ *parent, offset }, end());
auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point(*parent, offset + 1, m_start_container, m_start_offset); auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point({ *parent, offset + 1 }, start());
if (relative_position_to_end == RelativeBoundaryPointPosition::Before && relative_position_to_start == RelativeBoundaryPointPosition::After) if (relative_position_to_end == RelativeBoundaryPointPosition::Before && relative_position_to_start == RelativeBoundaryPointPosition::After)
return true; return true;
@ -520,8 +520,8 @@ WebIDL::ExceptionOr<bool> Range::is_point_in_range(GC::Ref<Node> node, WebIDL::U
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Node does not contain a child at offset {}", offset))); return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Node does not contain a child at offset {}", offset)));
// 4. If (node, offset) is before start or after end, return false. // 4. If (node, offset) is before start or after end, return false.
auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point(node, offset, m_start_container, m_start_offset); auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point({ node, offset }, start());
auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point(node, offset, m_end_container, m_end_offset); auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point({ node, offset }, end());
if (relative_position_to_start == RelativeBoundaryPointPosition::Before || relative_position_to_end == RelativeBoundaryPointPosition::After) if (relative_position_to_start == RelativeBoundaryPointPosition::Before || relative_position_to_end == RelativeBoundaryPointPosition::After)
return false; return false;
@ -545,12 +545,12 @@ WebIDL::ExceptionOr<WebIDL::Short> Range::compare_point(GC::Ref<Node> node, WebI
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Node does not contain a child at offset {}", offset))); return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Node does not contain a child at offset {}", offset)));
// 4. If (node, offset) is before start, return 1. // 4. If (node, offset) is before start, return 1.
auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point(node, offset, m_start_container, m_start_offset); auto relative_position_to_start = position_of_boundary_point_relative_to_other_boundary_point({ node, offset }, start());
if (relative_position_to_start == RelativeBoundaryPointPosition::Before) if (relative_position_to_start == RelativeBoundaryPointPosition::Before)
return -1; return -1;
// 5. If (node, offset) is after end, return 1. // 5. If (node, offset) is after end, return 1.
auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point(node, offset, m_end_container, m_end_offset); auto relative_position_to_end = position_of_boundary_point_relative_to_other_boundary_point({ node, offset }, end());
if (relative_position_to_end == RelativeBoundaryPointPosition::After) if (relative_position_to_end == RelativeBoundaryPointPosition::After)
return 1; return 1;
@ -794,11 +794,11 @@ bool Range::contains_node(GC::Ref<Node> node) const
return false; return false;
// and (node, 0) is after ranges start, // and (node, 0) is after ranges start,
if (position_of_boundary_point_relative_to_other_boundary_point(node, 0, m_start_container, m_start_offset) != RelativeBoundaryPointPosition::After) if (position_of_boundary_point_relative_to_other_boundary_point({ node, 0 }, start()) != RelativeBoundaryPointPosition::After)
return false; return false;
// and (node, nodes length) is before ranges end. // and (node, nodes length) is before ranges end.
if (position_of_boundary_point_relative_to_other_boundary_point(node, node->length(), m_end_container, m_end_offset) != RelativeBoundaryPointPosition::Before) if (position_of_boundary_point_relative_to_other_boundary_point({ node, static_cast<WebIDL::UnsignedLong>(node->length()) }, end()) != RelativeBoundaryPointPosition::Before)
return false; return false;
return true; return true;

View file

@ -22,7 +22,7 @@ enum class RelativeBoundaryPointPosition {
}; };
// https://dom.spec.whatwg.org/#concept-range-bp-position // https://dom.spec.whatwg.org/#concept-range-bp-position
RelativeBoundaryPointPosition position_of_boundary_point_relative_to_other_boundary_point(GC::Ref<Node> node_a, u32 offset_a, GC::Ref<Node> node_b, u32 offset_b); RelativeBoundaryPointPosition position_of_boundary_point_relative_to_other_boundary_point(BoundaryPoint a, BoundaryPoint b);
class Range final : public AbstractRange { class Range final : public AbstractRange {
WEB_PLATFORM_OBJECT(Range, AbstractRange); WEB_PLATFORM_OBJECT(Range, AbstractRange);

View file

@ -65,7 +65,7 @@ bool command_delete_action(DOM::Document& document, String const&)
} }
// 2. Canonicalize whitespace at the active range's start. // 2. Canonicalize whitespace at the active range's start.
canonicalize_whitespace(active_range.start_container(), active_range.start_offset()); canonicalize_whitespace(active_range.start());
// 3. Let node and offset be the active range's start node and offset. // 3. Let node and offset be the active range's start node and offset.
GC::Ptr<DOM::Node> node = active_range.start_container(); GC::Ptr<DOM::Node> node = active_range.start_container();

View file

@ -14,7 +14,6 @@
#include <LibWeb/DOM/DocumentType.h> #include <LibWeb/DOM/DocumentType.h>
#include <LibWeb/DOM/Element.h> #include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/ElementFactory.h> #include <LibWeb/DOM/ElementFactory.h>
#include <LibWeb/DOM/Range.h>
#include <LibWeb/DOM/Text.h> #include <LibWeb/DOM/Text.h>
#include <LibWeb/Editing/CommandNames.h> #include <LibWeb/Editing/CommandNames.h>
#include <LibWeb/Editing/Commands.h> #include <LibWeb/Editing/Commands.h>
@ -60,7 +59,7 @@ GC::Ref<DOM::Range> block_extend_a_range(DOM::Range& range)
} }
// 3. If (start node, start offset) is not a block start point, repeat the following steps: // 3. If (start node, start offset) is not a block start point, repeat the following steps:
if (!is_block_start_point(*start_node, start_offset)) { if (!is_block_start_point({ *start_node, start_offset })) {
do { do {
// 1. If start offset is zero, set it to start node's index, then set start node to its parent. // 1. If start offset is zero, set it to start node's index, then set start node to its parent.
if (start_offset == 0) { if (start_offset == 0) {
@ -74,7 +73,7 @@ GC::Ref<DOM::Range> block_extend_a_range(DOM::Range& range)
} }
// 3. If (start node, start offset) is a block boundary point, break from this loop. // 3. If (start node, start offset) is a block boundary point, break from this loop.
} while (!is_block_boundary_point(*start_node, start_offset)); } while (!is_block_boundary_point({ *start_node, start_offset }));
} }
// 4. While start offset is zero and start node's parent is not null, set start offset to start node's index, then // 4. While start offset is zero and start node's parent is not null, set start offset to start node's index, then
@ -97,7 +96,7 @@ GC::Ref<DOM::Range> block_extend_a_range(DOM::Range& range)
} }
// 6. If (end node, end offset) is not a block end point, repeat the following steps: // 6. If (end node, end offset) is not a block end point, repeat the following steps:
if (!is_block_end_point(*end_node, end_offset)) { if (!is_block_end_point({ *end_node, end_offset })) {
do { do {
// 1. If end offset is end node's length, set it to one plus end node's index, then set end node to its // 1. If end offset is end node's length, set it to one plus end node's index, then set end node to its
// parent. // parent.
@ -112,7 +111,7 @@ GC::Ref<DOM::Range> block_extend_a_range(DOM::Range& range)
} }
// 3. If (end node, end offset) is a block boundary point, break from this loop. // 3. If (end node, end offset) is a block boundary point, break from this loop.
} while (!is_block_boundary_point(*end_node, end_offset)); } while (!is_block_boundary_point({ *end_node, end_offset }));
} }
// 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 // 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
@ -222,8 +221,11 @@ String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_br
} }
// https://w3c.github.io/editing/docs/execCommand/#canonicalize-whitespace // https://w3c.github.io/editing/docs/execCommand/#canonicalize-whitespace
void canonicalize_whitespace(GC::Ref<DOM::Node> node, u32 offset, bool fix_collapsed_space) void canonicalize_whitespace(DOM::BoundaryPoint boundary, bool fix_collapsed_space)
{ {
auto node = boundary.node;
auto offset = boundary.offset;
// 1. If node is neither editable nor an editing host, abort these steps. // 1. If node is neither editable nor an editing host, abort these steps.
if (!node->is_editable_or_editing_host()) if (!node->is_editable_or_editing_host())
return; return;
@ -349,7 +351,7 @@ void canonicalize_whitespace(GC::Ref<DOM::Node> node, u32 offset, bool fix_colla
// end offset): // end offset):
if (fix_collapsed_space) { if (fix_collapsed_space) {
while (true) { while (true) {
auto relative_position = position_of_boundary_point_relative_to_other_boundary_point(*start_node, start_offset, *end_node, end_offset); auto relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point({ *start_node, start_offset }, { *end_node, end_offset });
if (relative_position != DOM::RelativeBoundaryPointPosition::Before) if (relative_position != DOM::RelativeBoundaryPointPosition::Before)
break; break;
@ -408,7 +410,7 @@ void canonicalize_whitespace(GC::Ref<DOM::Node> node, u32 offset, bool fix_colla
// 10. While (start node, start offset) is before (end node, end offset): // 10. While (start node, start offset) is before (end node, end offset):
while (true) { while (true) {
auto relative_position = position_of_boundary_point_relative_to_other_boundary_point(start_node, start_offset, end_node, end_offset); auto relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point({ start_node, start_offset }, { end_node, end_offset });
if (relative_position != DOM::RelativeBoundaryPointPosition::Before) if (relative_position != DOM::RelativeBoundaryPointPosition::Before)
break; break;
@ -468,19 +470,19 @@ void delete_the_selection(Selection& selection, bool block_merging, bool strip_w
return; return;
// 2. Canonicalize whitespace at the active range's start. // 2. Canonicalize whitespace at the active range's start.
canonicalize_whitespace(active_range()->start_container(), active_range()->start_offset()); canonicalize_whitespace(active_range()->start());
// 3. Canonicalize whitespace at the active range's end. // 3. Canonicalize whitespace at the active range's end.
canonicalize_whitespace(active_range()->end_container(), active_range()->end_offset()); canonicalize_whitespace(active_range()->end());
// 4. Let (start node, start offset) be the last equivalent point for the active range's start. // 4. Let (start node, start offset) be the last equivalent point for the active range's start.
auto start = last_equivalent_point({ active_range()->start_container(), active_range()->start_offset() }); auto start = last_equivalent_point(active_range()->start());
// 5. Let (end node, end offset) be the first equivalent point for the active range's end. // 5. Let (end node, end offset) be the first equivalent point for the active range's end.
auto end = first_equivalent_point({ active_range()->end_container(), active_range()->end_offset() }); auto end = first_equivalent_point(active_range()->end());
// 6. If (end node, end offset) is not after (start node, start offset): // 6. If (end node, end offset) is not after (start node, start offset):
auto relative_position = position_of_boundary_point_relative_to_other_boundary_point(end.node, end.offset, start.node, start.offset); auto relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point({ end.node, end.offset }, { start.node, start.offset });
if (relative_position != DOM::RelativeBoundaryPointPosition::After) { if (relative_position != DOM::RelativeBoundaryPointPosition::After) {
// 1. If direction is "forward", call collapseToStart() on the context object's selection. // 1. If direction is "forward", call collapseToStart() on the context object's selection.
if (direction == Selection::Direction::Forwards) { if (direction == Selection::Direction::Forwards) {
@ -559,7 +561,7 @@ void delete_the_selection(Selection& selection, bool block_merging, bool strip_w
MUST(static_cast<DOM::Text&>(*start.node).delete_data(start.offset, end.offset - start.offset)); MUST(static_cast<DOM::Text&>(*start.node).delete_data(start.offset, end.offset - start.offset));
// 2. Canonicalize whitespace at (start node, start offset), with fix collapsed space false. // 2. Canonicalize whitespace at (start node, start offset), with fix collapsed space false.
canonicalize_whitespace(start.node, start.offset, false); canonicalize_whitespace(start, false);
// 3. If direction is "forward", call collapseToStart() on the context object's selection. // 3. If direction is "forward", call collapseToStart() on the context object's selection.
if (direction == Selection::Direction::Forwards) { if (direction == Selection::Direction::Forwards) {
@ -637,10 +639,10 @@ void delete_the_selection(Selection& selection, bool block_merging, bool strip_w
MUST(static_cast<DOM::Text&>(*end.node).delete_data(0, end.offset)); MUST(static_cast<DOM::Text&>(*end.node).delete_data(0, end.offset));
// 27. Canonicalize whitespace at the active range's start, with fix collapsed space false. // 27. Canonicalize whitespace at the active range's start, with fix collapsed space false.
canonicalize_whitespace(active_range()->start_container(), active_range()->start_offset(), false); canonicalize_whitespace(active_range()->start(), false);
// 28. Canonicalize whitespace at the active range's end, with fix collapsed space false. // 28. Canonicalize whitespace at the active range's end, with fix collapsed space false.
canonicalize_whitespace(active_range()->end_container(), active_range()->end_offset(), false); canonicalize_whitespace(active_range()->end(), false);
// 30. If block merging is false, or start block or end block is null, or start block is not in the same editing // 30. If block merging is false, or start block or end block is null, or start block is not in the same editing
// host as end block, or start block and end block are the same: // host as end block, or start block and end block are the same:
@ -1046,7 +1048,7 @@ Optional<String> effective_command_value(GC::Ptr<DOM::Node> node, FlyString cons
} }
// https://w3c.github.io/editing/docs/execCommand/#first-equivalent-point // https://w3c.github.io/editing/docs/execCommand/#first-equivalent-point
BoundaryPoint first_equivalent_point(BoundaryPoint boundary_point) DOM::BoundaryPoint first_equivalent_point(DOM::BoundaryPoint boundary_point)
{ {
// 1. While (node, offset)'s previous equivalent point is not null, set (node, offset) to its previous equivalent // 1. While (node, offset)'s previous equivalent point is not null, set (node, offset) to its previous equivalent
// point. // point.
@ -1146,10 +1148,10 @@ void fix_disallowed_ancestors_of_node(GC::Ref<DOM::Node> node)
bool follows_a_line_break(GC::Ref<DOM::Node> node) bool follows_a_line_break(GC::Ref<DOM::Node> node)
{ {
// 1. Let offset be zero. // 1. Let offset be zero.
auto offset = 0; auto offset = 0u;
// 2. While (node, offset) is not a block boundary point: // 2. While (node, offset) is not a block boundary point:
while (!is_block_boundary_point(node, offset)) { while (!is_block_boundary_point({ node, offset })) {
// 1. If node has a visible child with index offset minus one, return false. // 1. If node has a visible child with index offset minus one, return false.
auto* offset_minus_one_child = node->child_at_index(offset - 1); auto* offset_minus_one_child = node->child_at_index(offset - 1);
if (offset_minus_one_child && is_visible_node(*offset_minus_one_child)) if (offset_minus_one_child && is_visible_node(*offset_minus_one_child))
@ -1385,22 +1387,22 @@ bool is_allowed_child_of_node(Variant<GC::Ref<DOM::Node>, FlyString> child, Vari
} }
// https://w3c.github.io/editing/docs/execCommand/#block-boundary-point // https://w3c.github.io/editing/docs/execCommand/#block-boundary-point
bool is_block_boundary_point(GC::Ref<DOM::Node> node, u32 offset) bool is_block_boundary_point(DOM::BoundaryPoint boundary_point)
{ {
// A boundary point is a block boundary point if it is either a block start point or a block end point. // A boundary point is a block boundary point if it is either a block start point or a block end point.
return is_block_start_point(node, offset) || is_block_end_point(node, offset); return is_block_start_point(boundary_point) || is_block_end_point(boundary_point);
} }
// https://w3c.github.io/editing/docs/execCommand/#block-end-point // https://w3c.github.io/editing/docs/execCommand/#block-end-point
bool is_block_end_point(GC::Ref<DOM::Node> node, u32 offset) bool is_block_end_point(DOM::BoundaryPoint boundary_point)
{ {
// A boundary point (node, offset) is a block end point if either node's parent is null and // A boundary point (node, offset) is a block end point if either node's parent is null and
// offset is node's length; // offset is node's length;
if (!node->parent() && offset == node->length()) if (!boundary_point.node->parent() && boundary_point.offset == boundary_point.node->length())
return true; return true;
// or node has a child with index offset, and that child is a visible block node. // or node has a child with index offset, and that child is a visible block node.
auto offset_child = node->child_at_index(offset); auto offset_child = boundary_point.node->child_at_index(boundary_point.offset);
return offset_child && is_visible_node(*offset_child) && is_block_node(*offset_child); return offset_child && is_visible_node(*offset_child) && is_block_node(*offset_child);
} }
@ -1423,19 +1425,17 @@ bool is_block_node(GC::Ref<DOM::Node> node)
} }
// https://w3c.github.io/editing/docs/execCommand/#block-start-point // https://w3c.github.io/editing/docs/execCommand/#block-start-point
bool is_block_start_point(GC::Ref<DOM::Node> node, u32 offset) bool is_block_start_point(DOM::BoundaryPoint boundary_point)
{ {
// A boundary point (node, offset) is a block start point if either node's parent is null and // A boundary point (node, offset) is a block start point if either node's parent is null and
// offset is zero; // offset is zero;
if (!node->parent() && offset == 0) if (!boundary_point.node->parent() && boundary_point.offset == 0)
return true; return true;
// or node has a child with index offset 1, and that child is either a visible block node or a // or node has a child with index offset 1, and that child is either a visible block node or a
// visible br. // visible br.
auto offset_minus_one_child = node->child_at_index(offset - 1); auto offset_minus_one_child = boundary_point.node->child_at_index(boundary_point.offset - 1);
if (!offset_minus_one_child) return offset_minus_one_child && is_visible_node(*offset_minus_one_child)
return false;
return is_visible_node(*offset_minus_one_child)
&& (is_block_node(*offset_minus_one_child) || is<HTML::HTMLBRElement>(*offset_minus_one_child)); && (is_block_node(*offset_minus_one_child) || is<HTML::HTMLBRElement>(*offset_minus_one_child));
} }
@ -1893,7 +1893,7 @@ bool is_whitespace_node(GC::Ref<DOM::Node> node)
} }
// https://w3c.github.io/editing/docs/execCommand/#last-equivalent-point // https://w3c.github.io/editing/docs/execCommand/#last-equivalent-point
BoundaryPoint last_equivalent_point(BoundaryPoint boundary_point) DOM::BoundaryPoint last_equivalent_point(DOM::BoundaryPoint boundary_point)
{ {
// 1. While (node, offset)'s next equivalent point is not null, set (node, offset) to its next equivalent point. // 1. While (node, offset)'s next equivalent point is not null, set (node, offset) to its next equivalent point.
while (true) { while (true) {
@ -1941,7 +1941,7 @@ void move_node_preserving_ranges(GC::Ref<DOM::Node> node, GC::Ref<DOM::Node> new
} }
// https://w3c.github.io/editing/docs/execCommand/#next-equivalent-point // https://w3c.github.io/editing/docs/execCommand/#next-equivalent-point
Optional<BoundaryPoint> next_equivalent_point(BoundaryPoint boundary_point) Optional<DOM::BoundaryPoint> next_equivalent_point(DOM::BoundaryPoint boundary_point)
{ {
// 1. If node's length is zero, return null. // 1. If node's length is zero, return null.
auto node = boundary_point.node; auto node = boundary_point.node;
@ -1952,13 +1952,13 @@ Optional<BoundaryPoint> next_equivalent_point(BoundaryPoint boundary_point)
// 3. If offset is node's length, and node's parent is not null, and node is an inline node, return (node's parent, // 3. If offset is node's length, and node's parent is not null, and node is an inline node, return (node's parent,
// 1 + node's index). // 1 + node's index).
if (boundary_point.offset == node_length && node->parent() && is_inline_node(*node)) if (boundary_point.offset == node_length && node->parent() && is_inline_node(*node))
return BoundaryPoint { *node->parent(), static_cast<WebIDL::UnsignedLong>(node->index() + 1) }; return DOM::BoundaryPoint { *node->parent(), static_cast<WebIDL::UnsignedLong>(node->index() + 1) };
// 5. If node has a child with index offset, and that child's length is not zero, and that child is an inline node, // 5. If node has a child with index offset, and that child's length is not zero, and that child is an inline node,
// return (that child, 0). // return (that child, 0).
auto child_at_offset = node->child_at_index(boundary_point.offset); auto child_at_offset = node->child_at_index(boundary_point.offset);
if (child_at_offset && child_at_offset->length() != 0 && is_inline_node(*child_at_offset)) if (child_at_offset && child_at_offset->length() != 0 && is_inline_node(*child_at_offset))
return BoundaryPoint { *child_at_offset, 0 }; return DOM::BoundaryPoint { *child_at_offset, 0 };
// 7. Return null. // 7. Return null.
return {}; return {};
@ -2014,10 +2014,10 @@ void normalize_sublists_in_node(GC::Ref<DOM::Element> item)
bool precedes_a_line_break(GC::Ref<DOM::Node> node) bool precedes_a_line_break(GC::Ref<DOM::Node> node)
{ {
// 1. Let offset be node's length. // 1. Let offset be node's length.
auto offset = node->length(); WebIDL::UnsignedLong offset = node->length();
// 2. While (node, offset) is not a block boundary point: // 2. While (node, offset) is not a block boundary point:
while (!is_block_boundary_point(node, offset)) { while (!is_block_boundary_point({ node, offset })) {
// 1. If node has a visible child with index offset, return false. // 1. If node has a visible child with index offset, return false.
auto* offset_child = node->child_at_index(offset); auto* offset_child = node->child_at_index(offset);
if (offset_child && is_visible_node(*offset_child)) if (offset_child && is_visible_node(*offset_child))
@ -2042,7 +2042,7 @@ bool precedes_a_line_break(GC::Ref<DOM::Node> node)
} }
// https://w3c.github.io/editing/docs/execCommand/#previous-equivalent-point // https://w3c.github.io/editing/docs/execCommand/#previous-equivalent-point
Optional<BoundaryPoint> previous_equivalent_point(BoundaryPoint boundary_point) Optional<DOM::BoundaryPoint> previous_equivalent_point(DOM::BoundaryPoint boundary_point)
{ {
// 1. If node's length is zero, return null. // 1. If node's length is zero, return null.
auto node = boundary_point.node; auto node = boundary_point.node;
@ -2053,13 +2053,13 @@ Optional<BoundaryPoint> previous_equivalent_point(BoundaryPoint boundary_point)
// 2. If offset is 0, and node's parent is not null, and node is an inline node, return (node's parent, node's // 2. If offset is 0, and node's parent is not null, and node is an inline node, return (node's parent, node's
// index). // index).
if (boundary_point.offset == 0 && node->parent() && is_inline_node(*node)) if (boundary_point.offset == 0 && node->parent() && is_inline_node(*node))
return BoundaryPoint { *node->parent(), static_cast<WebIDL::UnsignedLong>(node->index()) }; return DOM::BoundaryPoint { *node->parent(), static_cast<WebIDL::UnsignedLong>(node->index()) };
// 3. If node has a child with index offset 1, and that child's length is not zero, and that child is an inline // 3. If node has a child with index offset 1, and that child's length is not zero, and that child is an inline
// node, return (that child, that child's length). // node, return (that child, that child's length).
auto child_at_offset = node->child_at_index(boundary_point.offset - 1); auto child_at_offset = node->child_at_index(boundary_point.offset - 1);
if (child_at_offset && child_at_offset->length() != 0 && is_inline_node(*child_at_offset)) if (child_at_offset && child_at_offset->length() != 0 && is_inline_node(*child_at_offset))
return BoundaryPoint { *child_at_offset, static_cast<WebIDL::UnsignedLong>(child_at_offset->length()) }; return DOM::BoundaryPoint { *child_at_offset, static_cast<WebIDL::UnsignedLong>(child_at_offset->length()) };
// 4. Return null. // 4. Return null.
return {}; return {};

View file

@ -8,6 +8,7 @@
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibWeb/DOM/Node.h> #include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/Range.h>
#include <LibWeb/Selection/Selection.h> #include <LibWeb/Selection/Selection.h>
namespace Web::Editing { namespace Web::Editing {
@ -27,32 +28,25 @@ struct RecordedOverride {
using Selection::Selection; using Selection::Selection;
// https://dom.spec.whatwg.org/#concept-range-bp
// FIXME: This should be defined by DOM::Range
struct BoundaryPoint {
GC::Ref<DOM::Node> node;
WebIDL::UnsignedLong offset;
};
// Below algorithms are specified here: // Below algorithms are specified here:
// https://w3c.github.io/editing/docs/execCommand/#assorted-common-algorithms // https://w3c.github.io/editing/docs/execCommand/#assorted-common-algorithms
GC::Ref<DOM::Range> block_extend_a_range(DOM::Range&); GC::Ref<DOM::Range> block_extend_a_range(DOM::Range&);
GC::Ptr<DOM::Node> block_node_of_node(GC::Ref<DOM::Node>); GC::Ptr<DOM::Node> block_node_of_node(GC::Ref<DOM::Node>);
String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_breaking_end); String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_breaking_end);
void canonicalize_whitespace(GC::Ref<DOM::Node>, u32 offset, bool fix_collapsed_space = true); void canonicalize_whitespace(DOM::BoundaryPoint, bool fix_collapsed_space = true);
void delete_the_selection(Selection&, bool block_merging = true, bool strip_wrappers = true, void delete_the_selection(Selection&, bool block_merging = true, bool strip_wrappers = true,
Selection::Direction direction = Selection::Direction::Forwards); Selection::Direction direction = Selection::Direction::Forwards);
GC::Ptr<DOM::Node> editing_host_of_node(GC::Ref<DOM::Node>); GC::Ptr<DOM::Node> editing_host_of_node(GC::Ref<DOM::Node>);
Optional<String> effective_command_value(GC::Ptr<DOM::Node>, FlyString const& command); Optional<String> effective_command_value(GC::Ptr<DOM::Node>, FlyString const& command);
BoundaryPoint first_equivalent_point(BoundaryPoint); DOM::BoundaryPoint first_equivalent_point(DOM::BoundaryPoint);
void fix_disallowed_ancestors_of_node(GC::Ref<DOM::Node>); void fix_disallowed_ancestors_of_node(GC::Ref<DOM::Node>);
bool follows_a_line_break(GC::Ref<DOM::Node>); bool follows_a_line_break(GC::Ref<DOM::Node>);
bool is_allowed_child_of_node(Variant<GC::Ref<DOM::Node>, FlyString> child, Variant<GC::Ref<DOM::Node>, FlyString> parent); bool is_allowed_child_of_node(Variant<GC::Ref<DOM::Node>, FlyString> child, Variant<GC::Ref<DOM::Node>, FlyString> parent);
bool is_block_boundary_point(GC::Ref<DOM::Node>, u32 offset); bool is_block_boundary_point(DOM::BoundaryPoint);
bool is_block_end_point(GC::Ref<DOM::Node>, u32 offset); bool is_block_end_point(DOM::BoundaryPoint);
bool is_block_node(GC::Ref<DOM::Node>); bool is_block_node(GC::Ref<DOM::Node>);
bool is_block_start_point(GC::Ref<DOM::Node>, u32 offset); bool is_block_start_point(DOM::BoundaryPoint);
bool is_collapsed_block_prop(GC::Ref<DOM::Node>); bool is_collapsed_block_prop(GC::Ref<DOM::Node>);
bool is_collapsed_line_break(GC::Ref<DOM::Node>); bool is_collapsed_line_break(GC::Ref<DOM::Node>);
bool is_collapsed_whitespace_node(GC::Ref<DOM::Node>); bool is_collapsed_whitespace_node(GC::Ref<DOM::Node>);
@ -70,12 +64,12 @@ bool is_prohibited_paragraph_child_name(FlyString const&);
bool is_single_line_container(GC::Ref<DOM::Node>); bool is_single_line_container(GC::Ref<DOM::Node>);
bool is_visible_node(GC::Ref<DOM::Node>); bool is_visible_node(GC::Ref<DOM::Node>);
bool is_whitespace_node(GC::Ref<DOM::Node>); bool is_whitespace_node(GC::Ref<DOM::Node>);
BoundaryPoint last_equivalent_point(BoundaryPoint); DOM::BoundaryPoint last_equivalent_point(DOM::BoundaryPoint);
void move_node_preserving_ranges(GC::Ref<DOM::Node>, GC::Ref<DOM::Node> new_parent, u32 new_index); void move_node_preserving_ranges(GC::Ref<DOM::Node>, GC::Ref<DOM::Node> new_parent, u32 new_index);
Optional<BoundaryPoint> next_equivalent_point(BoundaryPoint); Optional<DOM::BoundaryPoint> next_equivalent_point(DOM::BoundaryPoint);
void normalize_sublists_in_node(GC::Ref<DOM::Element>); void normalize_sublists_in_node(GC::Ref<DOM::Element>);
bool precedes_a_line_break(GC::Ref<DOM::Node>); bool precedes_a_line_break(GC::Ref<DOM::Node>);
Optional<BoundaryPoint> previous_equivalent_point(BoundaryPoint); Optional<DOM::BoundaryPoint> previous_equivalent_point(DOM::BoundaryPoint);
Vector<RecordedOverride> record_current_states_and_values(GC::Ref<DOM::Range>); Vector<RecordedOverride> record_current_states_and_values(GC::Ref<DOM::Range>);
Vector<RecordedNodeValue> record_the_values_of_nodes(Vector<GC::Ref<DOM::Node>> const&); Vector<RecordedNodeValue> record_the_values_of_nodes(Vector<GC::Ref<DOM::Node>> const&);
void remove_extraneous_line_breaks_at_the_end_of_node(GC::Ref<DOM::Node>); void remove_extraneous_line_breaks_at_the_end_of_node(GC::Ref<DOM::Node>);

View file

@ -296,7 +296,7 @@ WebIDL::ExceptionOr<void> Selection::extend(GC::Ref<DOM::Node> node, unsigned of
TRY(new_range->set_end(new_focus_node, new_focus_offset)); TRY(new_range->set_end(new_focus_node, new_focus_offset));
} }
// 6. Otherwise, if oldAnchor is before or equal to newFocus, set the start newRange's start to oldAnchor, then set its end to newFocus. // 6. Otherwise, if oldAnchor is before or equal to newFocus, set the start newRange's start to oldAnchor, then set its end to newFocus.
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) { else if (DOM::position_of_boundary_point_relative_to_other_boundary_point({ old_anchor_node, old_anchor_offset }, { new_focus_node, new_focus_offset }) != DOM::RelativeBoundaryPointPosition::After) {
TRY(new_range->set_start(old_anchor_node, old_anchor_offset)); TRY(new_range->set_start(old_anchor_node, old_anchor_offset));
TRY(new_range->set_end(new_focus_node, new_focus_offset)); TRY(new_range->set_end(new_focus_node, new_focus_offset));
} }
@ -310,7 +310,7 @@ WebIDL::ExceptionOr<void> Selection::extend(GC::Ref<DOM::Node> node, unsigned of
set_range(new_range); set_range(new_range);
// 9. If newFocus is before oldAnchor, set this's direction to backwards. Otherwise, set it to forwards. // 9. If newFocus is before oldAnchor, set this's direction to backwards. Otherwise, set it to forwards.
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) { if (DOM::position_of_boundary_point_relative_to_other_boundary_point({ new_focus_node, new_focus_offset }, { old_anchor_node, old_anchor_offset }) == DOM::RelativeBoundaryPointPosition::Before) {
m_direction = Direction::Backwards; m_direction = Direction::Backwards;
} else { } else {
m_direction = Direction::Forwards; m_direction = Direction::Forwards;
@ -339,7 +339,7 @@ WebIDL::ExceptionOr<void> Selection::set_base_and_extent(GC::Ref<DOM::Node> anch
auto new_range = DOM::Range::create(*m_document); auto new_range = DOM::Range::create(*m_document);
// 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. // 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.
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); 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 });
if (position_of_anchor_relative_to_focus == DOM::RelativeBoundaryPointPosition::Before) { if (position_of_anchor_relative_to_focus == DOM::RelativeBoundaryPointPosition::Before) {
TRY(new_range->set_start(anchor_node, anchor_offset)); TRY(new_range->set_start(anchor_node, anchor_offset));
TRY(new_range->set_end(focus_node, focus_offset)); TRY(new_range->set_end(focus_node, focus_offset));
@ -414,16 +414,8 @@ bool Selection::contains_node(GC::Ref<DOM::Node> node, bool allow_partial_contai
// start of its range is before or visually equivalent to the first boundary point in the node // start of its range is before or visually equivalent to the first boundary point in the node
// and end of its range is after or visually equivalent to the last boundary point in the node. // and end of its range is after or visually equivalent to the last boundary point in the node.
if (!allow_partial_containment) { if (!allow_partial_containment) {
auto start_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point( auto start_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(m_range->start(), { node, 0 });
*m_range->start_container(), auto end_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(m_range->end(), { node, static_cast<WebIDL::UnsignedLong>(node->length()) });
m_range->start_offset(),
node,
0);
auto end_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(
*m_range->end_container(),
m_range->end_offset(),
node,
node->length());
return (start_relative_position == DOM::RelativeBoundaryPointPosition::Before || start_relative_position == DOM::RelativeBoundaryPointPosition::Equal) return (start_relative_position == DOM::RelativeBoundaryPointPosition::Before || start_relative_position == DOM::RelativeBoundaryPointPosition::Equal)
&& (end_relative_position == DOM::RelativeBoundaryPointPosition::Equal || end_relative_position == DOM::RelativeBoundaryPointPosition::After); && (end_relative_position == DOM::RelativeBoundaryPointPosition::Equal || end_relative_position == DOM::RelativeBoundaryPointPosition::After);
@ -433,16 +425,8 @@ bool Selection::contains_node(GC::Ref<DOM::Node> node, bool allow_partial_contai
// start of its range is before or visually equivalent to the last boundary point in the node // start of its range is before or visually equivalent to the last boundary point in the node
// and end of its range is after or visually equivalent to the first boundary point in the node. // and end of its range is after or visually equivalent to the first boundary point in the node.
auto start_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point( auto start_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(m_range->start(), { node, static_cast<WebIDL::UnsignedLong>(node->length()) });
*m_range->start_container(), auto end_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(m_range->end(), { node, 0 });
m_range->start_offset(),
node,
node->length());
auto end_relative_position = DOM::position_of_boundary_point_relative_to_other_boundary_point(
*m_range->end_container(),
m_range->end_offset(),
node,
0);
return (start_relative_position == DOM::RelativeBoundaryPointPosition::Before || start_relative_position == DOM::RelativeBoundaryPointPosition::Equal) return (start_relative_position == DOM::RelativeBoundaryPointPosition::Before || start_relative_position == DOM::RelativeBoundaryPointPosition::Equal)
&& (end_relative_position == DOM::RelativeBoundaryPointPosition::Equal || end_relative_position == DOM::RelativeBoundaryPointPosition::After); && (end_relative_position == DOM::RelativeBoundaryPointPosition::Equal || end_relative_position == DOM::RelativeBoundaryPointPosition::After);