/src/serenity/Userland/Libraries/LibWeb/DOM/Text.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibUnicode/CharacterTypes.h> |
8 | | #include <LibWeb/Bindings/Intrinsics.h> |
9 | | #include <LibWeb/Bindings/TextPrototype.h> |
10 | | #include <LibWeb/DOM/Range.h> |
11 | | #include <LibWeb/DOM/Text.h> |
12 | | #include <LibWeb/HTML/Scripting/Environments.h> |
13 | | #include <LibWeb/HTML/Window.h> |
14 | | #include <LibWeb/Layout/TextNode.h> |
15 | | |
16 | | namespace Web::DOM { |
17 | | |
18 | | JS_DEFINE_ALLOCATOR(Text); |
19 | | |
20 | | Text::Text(Document& document, String const& data) |
21 | 0 | : CharacterData(document, NodeType::TEXT_NODE, data) |
22 | 0 | { |
23 | 0 | } |
24 | | |
25 | | Text::Text(Document& document, NodeType type, String const& data) |
26 | 0 | : CharacterData(document, type, data) |
27 | 0 | { |
28 | 0 | } |
29 | | |
30 | | void Text::initialize(JS::Realm& realm) |
31 | 0 | { |
32 | 0 | Base::initialize(realm); |
33 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(Text); |
34 | 0 | } |
35 | | |
36 | | void Text::visit_edges(Cell::Visitor& visitor) |
37 | 0 | { |
38 | 0 | Base::visit_edges(visitor); |
39 | 0 | SlottableMixin::visit_edges(visitor); |
40 | 0 | visitor.visit(m_owner); |
41 | 0 | } |
42 | | |
43 | | // https://dom.spec.whatwg.org/#dom-text-text |
44 | | WebIDL::ExceptionOr<JS::NonnullGCPtr<Text>> Text::construct_impl(JS::Realm& realm, String const& data) |
45 | 0 | { |
46 | | // The new Text(data) constructor steps are to set this’s data to data and this’s node document to current global object’s associated Document. |
47 | 0 | auto& window = verify_cast<HTML::Window>(HTML::current_global_object()); |
48 | 0 | return realm.heap().allocate<Text>(realm, window.associated_document(), data); |
49 | 0 | } |
50 | | |
51 | | EditableTextNodeOwner* Text::editable_text_node_owner() |
52 | 0 | { |
53 | 0 | if (!m_owner) |
54 | 0 | return nullptr; |
55 | 0 | EditableTextNodeOwner* owner = dynamic_cast<EditableTextNodeOwner*>(m_owner.ptr()); |
56 | 0 | VERIFY(owner); |
57 | 0 | return owner; |
58 | 0 | } |
59 | | |
60 | | // https://dom.spec.whatwg.org/#dom-text-splittext |
61 | | // https://dom.spec.whatwg.org/#concept-text-split |
62 | | WebIDL::ExceptionOr<JS::NonnullGCPtr<Text>> Text::split_text(size_t offset) |
63 | 0 | { |
64 | | // 1. Let length be node’s length. |
65 | 0 | auto length = this->length(); |
66 | | |
67 | | // 2. If offset is greater than length, then throw an "IndexSizeError" DOMException. |
68 | 0 | if (offset > length) |
69 | 0 | return WebIDL::IndexSizeError::create(realm(), "Split offset is greater than length"_string); |
70 | | |
71 | | // 3. Let count be length minus offset. |
72 | 0 | auto count = length - offset; |
73 | | |
74 | | // 4. Let new data be the result of substringing data with node node, offset offset, and count count. |
75 | 0 | auto new_data = TRY(substring_data(offset, count)); |
76 | | |
77 | | // 5. Let new node be a new Text node, with the same node document as node. Set new node’s data to new data. |
78 | 0 | auto new_node = heap().allocate<Text>(realm(), document(), new_data); |
79 | | |
80 | | // 6. Let parent be node’s parent. |
81 | 0 | JS::GCPtr<Node> parent = this->parent(); |
82 | | |
83 | | // 7. If parent is not null, then: |
84 | 0 | if (parent) { |
85 | | // 1. Insert new node into parent before node’s next sibling. |
86 | 0 | parent->insert_before(*new_node, next_sibling()); |
87 | | |
88 | | // 2. For each live range whose start node is node and start offset is greater than offset, set its start node to new node and decrease its start offset by offset. |
89 | 0 | for (auto& range : Range::live_ranges()) { |
90 | 0 | if (range->start_container() == this && range->start_offset() > offset) |
91 | 0 | TRY(range->set_start(*new_node, range->start_offset() - offset)); |
92 | 0 | } |
93 | | |
94 | | // 3. For each live range whose end node is node and end offset is greater than offset, set its end node to new node and decrease its end offset by offset. |
95 | 0 | for (auto& range : Range::live_ranges()) { |
96 | 0 | if (range->end_container() == this && range->end_offset() > offset) |
97 | 0 | TRY(range->set_end(*new_node, range->end_offset() - offset)); |
98 | 0 | } |
99 | | |
100 | | // 4. For each live range whose start node is parent and start offset is equal to the index of node plus 1, increase its start offset by 1. |
101 | 0 | for (auto& range : Range::live_ranges()) { |
102 | 0 | if (range->start_container() == parent.ptr() && range->start_offset() == index() + 1) |
103 | 0 | TRY(range->set_start(*range->start_container(), range->start_offset() + 1)); |
104 | 0 | } |
105 | | |
106 | | // 5. For each live range whose end node is parent and end offset is equal to the index of node plus 1, increase its end offset by 1. |
107 | 0 | for (auto& range : Range::live_ranges()) { |
108 | 0 | if (range->end_container() == parent.ptr() && range->end_offset() == index() + 1) { |
109 | 0 | TRY(range->set_end(*range->end_container(), range->end_offset() + 1)); |
110 | 0 | } |
111 | 0 | } |
112 | 0 | } |
113 | | |
114 | | // 8. Replace data with node node, offset offset, count count, and data the empty string. |
115 | 0 | TRY(replace_data(offset, count, String {})); |
116 | | |
117 | | // 9. Return new node. |
118 | 0 | return new_node; |
119 | 0 | } |
120 | | |
121 | | // https://dom.spec.whatwg.org/#dom-text-wholetext |
122 | | String Text::whole_text() |
123 | 0 | { |
124 | | // https://dom.spec.whatwg.org/#contiguous-text-nodes |
125 | | // The contiguous Text nodes of a node node are node, node’s previous sibling Text node, if any, and its contiguous |
126 | | // Text nodes, and node’s next sibling Text node, if any, and its contiguous Text nodes, avoiding any duplicates. |
127 | 0 | Vector<Text*> nodes; |
128 | |
|
129 | 0 | nodes.append(this); |
130 | |
|
131 | 0 | auto* current_node = previous_sibling(); |
132 | 0 | while (current_node && (current_node->is_text() || current_node->is_cdata_section())) { |
133 | 0 | nodes.append(static_cast<Text*>(current_node)); |
134 | 0 | current_node = current_node->previous_sibling(); |
135 | 0 | } |
136 | | |
137 | | // Reverse nodes so they are in tree order |
138 | 0 | nodes.reverse(); |
139 | |
|
140 | 0 | current_node = next_sibling(); |
141 | 0 | while (current_node && (current_node->is_text() || current_node->is_cdata_section())) { |
142 | 0 | nodes.append(static_cast<Text*>(current_node)); |
143 | 0 | current_node = current_node->next_sibling(); |
144 | 0 | } |
145 | |
|
146 | 0 | StringBuilder builder; |
147 | 0 | for (auto const& text_node : nodes) |
148 | 0 | builder.append(text_node->data()); |
149 | |
|
150 | 0 | return MUST(builder.to_string()); |
151 | 0 | } |
152 | | |
153 | | // https://html.spec.whatwg.org/multipage/dom.html#text-node-directionality |
154 | | Optional<Element::Directionality> Text::directionality() const |
155 | 0 | { |
156 | | // 1. If text's data does not contain a code point whose bidirectional character type is L, AL, or R, then return null. |
157 | | // 2. Let codePoint be the first code point in text's data whose bidirectional character type is L, AL, or R. |
158 | 0 | Optional<Unicode::BidiClass> found_character_bidi_class; |
159 | 0 | for (auto code_point : Utf8View(data())) { |
160 | 0 | auto bidi_class = Unicode::bidirectional_class(code_point); |
161 | 0 | if (first_is_one_of(bidi_class, Unicode::BidiClass::LeftToRight, Unicode::BidiClass::RightToLeftArabic, Unicode::BidiClass::RightToLeft)) { |
162 | 0 | found_character_bidi_class = bidi_class; |
163 | 0 | break; |
164 | 0 | } |
165 | 0 | } |
166 | 0 | if (!found_character_bidi_class.has_value()) |
167 | 0 | return {}; |
168 | | |
169 | | // 3. If codePoint is of bidirectional character type AL or R, then return 'rtl'. |
170 | 0 | if (first_is_one_of(*found_character_bidi_class, Unicode::BidiClass::RightToLeftArabic, Unicode::BidiClass::RightToLeft)) |
171 | 0 | return Element::Directionality::Rtl; |
172 | | |
173 | | // 4. If codePoint is of bidirectional character type L, then return 'ltr'. |
174 | | // NOTE: codePoint should always be of bidirectional character type L by this point, so we can just return 'ltr' here. |
175 | 0 | VERIFY(*found_character_bidi_class == Unicode::BidiClass::LeftToRight); |
176 | 0 | return Element::Directionality::Ltr; |
177 | 0 | } |
178 | | |
179 | | } |