Serenity Operating System
1/*
2 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <LibWeb/Bindings/CharacterDataPrototype.h>
8#include <LibWeb/DOM/CharacterData.h>
9#include <LibWeb/DOM/Document.h>
10#include <LibWeb/DOM/MutationType.h>
11#include <LibWeb/DOM/Range.h>
12#include <LibWeb/DOM/StaticNodeList.h>
13
14namespace Web::DOM {
15
16CharacterData::CharacterData(Document& document, NodeType type, DeprecatedString const& data)
17 : Node(document, type)
18 , m_data(data)
19{
20}
21
22JS::ThrowCompletionOr<void> CharacterData::initialize(JS::Realm& realm)
23{
24 MUST_OR_THROW_OOM(Base::initialize(realm));
25 set_prototype(&Bindings::ensure_web_prototype<Bindings::CharacterDataPrototype>(realm, "CharacterData"));
26
27 return {};
28}
29
30// https://dom.spec.whatwg.org/#dom-characterdata-data
31void CharacterData::set_data(DeprecatedString data)
32{
33 // [The data] setter must replace data with node this, offset 0, count this’s length, and data new value.
34 // NOTE: Since the offset is 0, it can never be above data's length, so this can never throw.
35 // NOTE: Setting the data to the same value as the current data still causes a mutation observer callback.
36 // FIXME: Figure out a way to make this a no-op again if the passed in data is the same as the current data.
37 MUST(replace_data(0, m_data.length(), data));
38}
39
40// https://dom.spec.whatwg.org/#concept-cd-substring
41WebIDL::ExceptionOr<DeprecatedString> CharacterData::substring_data(size_t offset, size_t count) const
42{
43 // 1. Let length be node’s length.
44 auto length = this->length();
45
46 // 2. If offset is greater than length, then throw an "IndexSizeError" DOMException.
47 if (offset > length)
48 return WebIDL::IndexSizeError::create(realm(), "Substring offset out of range.");
49
50 // 3. If offset plus count is greater than length, return a string whose value is the code units from the offsetth code unit
51 // to the end of node’s data, and then return.
52 if (offset + count > length)
53 return m_data.substring(offset);
54
55 // 4. Return a string whose value is the code units from the offsetth code unit to the offset+countth code unit in node’s data.
56 return m_data.substring(offset, count);
57}
58
59// https://dom.spec.whatwg.org/#concept-cd-replace
60WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t count, DeprecatedString const& data)
61{
62 // 1. Let length be node’s length.
63 auto length = this->length();
64
65 // 2. If offset is greater than length, then throw an "IndexSizeError" DOMException.
66 if (offset > length)
67 return WebIDL::IndexSizeError::create(realm(), "Replacement offset out of range.");
68
69 // 3. If offset plus count is greater than length, then set count to length minus offset.
70 if (offset + count > length)
71 count = length - offset;
72
73 // 4. Queue a mutation record of "characterData" for node with null, null, node’s data, « », « », null, and null.
74 auto added_node_list = TRY(StaticNodeList::create(realm(), {}));
75 auto removed_node_list = TRY(StaticNodeList::create(realm(), {}));
76 queue_mutation_record(MutationType::characterData, {}, {}, m_data, added_node_list, removed_node_list, nullptr, nullptr);
77
78 // 5. Insert data into node’s data after offset code units.
79 // 6. Let delete offset be offset + data’s length.
80 // 7. Starting from delete offset code units, remove count code units from node’s data.
81 StringBuilder builder;
82 builder.append(this->data().substring_view(0, offset));
83 builder.append(data);
84 builder.append(this->data().substring_view(offset + count));
85 m_data = builder.to_deprecated_string();
86
87 // 8. For each live range whose start node is node and start offset is greater than offset but less than or equal to offset plus count, set its start offset to offset.
88 for (auto& range : Range::live_ranges()) {
89 if (range->start_container() == this && range->start_offset() > offset && range->start_offset() <= (offset + count))
90 TRY(range->set_start(*range->start_container(), offset));
91 }
92
93 // 9. For each live range whose end node is node and end offset is greater than offset but less than or equal to offset plus count, set its end offset to offset.
94 for (auto& range : Range::live_ranges()) {
95 if (range->end_container() == this && range->end_offset() > offset && range->end_offset() <= (offset + count))
96 TRY(range->set_end(*range->end_container(), range->end_offset()));
97 }
98
99 // 10. For each live range whose start node is node and start offset is greater than offset plus count, increase its start offset by data’s length and decrease it by count.
100 for (auto& range : Range::live_ranges()) {
101 if (range->start_container() == this && range->start_offset() > (offset + count))
102 TRY(range->set_start(*range->start_container(), range->start_offset() + data.length() - count));
103 }
104
105 // 11. For each live range whose end node is node and end offset is greater than offset plus count, increase its end offset by data’s length and decrease it by count.
106 for (auto& range : Range::live_ranges()) {
107 if (range->end_container() == this && range->end_offset() > (offset + count))
108 TRY(range->set_end(*range->end_container(), range->end_offset() + data.length() - count));
109 }
110
111 // 12. If node’s parent is non-null, then run the children changed steps for node’s parent.
112 if (parent())
113 parent()->children_changed();
114
115 set_needs_style_update(true);
116 document().set_needs_layout();
117 return {};
118}
119
120// https://dom.spec.whatwg.org/#dom-characterdata-appenddata
121WebIDL::ExceptionOr<void> CharacterData::append_data(DeprecatedString const& data)
122{
123 // The appendData(data) method steps are to replace data with node this, offset this’s length, count 0, and data data.
124 return replace_data(m_data.length(), 0, data);
125}
126
127// https://dom.spec.whatwg.org/#dom-characterdata-insertdata
128WebIDL::ExceptionOr<void> CharacterData::insert_data(size_t offset, DeprecatedString const& data)
129{
130 // The insertData(offset, data) method steps are to replace data with node this, offset offset, count 0, and data data.
131 return replace_data(offset, 0, data);
132}
133
134// https://dom.spec.whatwg.org/#dom-characterdata-deletedata
135WebIDL::ExceptionOr<void> CharacterData::delete_data(size_t offset, size_t count)
136{
137 // The deleteData(offset, count) method steps are to replace data with node this, offset offset, count count, and data the empty string.
138 return replace_data(offset, count, DeprecatedString::empty());
139}
140
141}