LibWeb+LibUnicode+WebContent: Port DOM:CharacterData to UTF-16

This replaces the underlying storage of CharacterData with Utf16String
and deals with the fallout.
This commit is contained in:
Timothy Flynn 2025-07-24 12:05:52 -04:00 committed by Jelle Raaijmakers
commit 8b6e3cb735
Notes: github-actions[bot] 2025-07-24 17:01:33 +00:00
56 changed files with 233 additions and 245 deletions

View file

@ -247,7 +247,7 @@ NonnullOwnPtr<Segmenter> Segmenter::create(StringView locale, SegmenterGranulari
return make<SegmenterImpl>(segmenter.release_nonnull(), segmenter_granularity); return make<SegmenterImpl>(segmenter.release_nonnull(), segmenter_granularity);
} }
bool Segmenter::should_continue_beyond_word(Utf8View const& word) bool Segmenter::should_continue_beyond_word(Utf16View const& word)
{ {
for (auto code_point : word) { for (auto code_point : word) {
if (!code_point_has_punctuation_general_category(code_point) && !code_point_has_separator_general_category(code_point)) if (!code_point_has_punctuation_general_category(code_point) && !code_point_has_separator_general_category(code_point))

View file

@ -27,7 +27,7 @@ public:
static NonnullOwnPtr<Segmenter> create(StringView locale, SegmenterGranularity segmenter_granularity); static NonnullOwnPtr<Segmenter> create(StringView locale, SegmenterGranularity segmenter_granularity);
virtual ~Segmenter() = default; virtual ~Segmenter() = default;
static bool should_continue_beyond_word(Utf8View const&); static bool should_continue_beyond_word(Utf16View const&);
SegmenterGranularity segmenter_granularity() const { return m_segmenter_granularity; } SegmenterGranularity segmenter_granularity() const { return m_segmenter_granularity; }

View file

@ -60,9 +60,9 @@ JS::ThrowCompletionOr<GC::Ref<JS::Object>> OptionConstructor::construct(Function
GC::Ref<HTML::HTMLOptionElement> option_element = as<HTML::HTMLOptionElement>(*element); GC::Ref<HTML::HTMLOptionElement> option_element = as<HTML::HTMLOptionElement>(*element);
// 3. If text is not the empty string, then append to option a new Text node whose data is text. // 3. If text is not the empty string, then append to option a new Text node whose data is text.
auto text = TRY(text_value.to_string(vm)); auto text = TRY(text_value.to_utf16_string(vm));
if (!text.is_empty()) { if (!text.is_empty()) {
auto new_text_node = realm.create<DOM::Text>(document, text); auto new_text_node = realm.create<DOM::Text>(document, move(text));
MUST(option_element->append_child(*new_text_node)); MUST(option_element->append_child(*new_text_node));
} }

View file

@ -51,12 +51,11 @@ void AccessibilityTreeNode::serialize_tree_as_json(JsonObjectSerializer<StringBu
} else { } else {
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
} else if (value()->is_text()) { } else if (value()->is_text()) {
MUST(object.add("type"sv, "text"sv)); MUST(object.add("type"sv, "text"sv));
auto const* text_node = static_cast<DOM::Text const*>(value().ptr()); auto const* text_node = static_cast<DOM::Text const*>(value().ptr());
MUST(object.add("text"sv, text_node->data())); MUST(object.add("text"sv, text_node->data().to_utf8()));
} }
if (value()->has_child_nodes()) { if (value()->has_child_nodes()) {

View file

@ -12,8 +12,8 @@ namespace Web::DOM {
GC_DEFINE_ALLOCATOR(CDATASection); GC_DEFINE_ALLOCATOR(CDATASection);
CDATASection::CDATASection(Document& document, String const& data) CDATASection::CDATASection(Document& document, Utf16String data)
: Text(document, NodeType::CDATA_SECTION_NODE, data) : Text(document, NodeType::CDATA_SECTION_NODE, move(data))
{ {
} }

View file

@ -22,7 +22,7 @@ public:
virtual FlyString node_name() const override { return "#cdata-section"_fly_string; } virtual FlyString node_name() const override { return "#cdata-section"_fly_string; }
private: private:
CDATASection(Document&, String const&); CDATASection(Document&, Utf16String);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
}; };

View file

@ -17,9 +17,9 @@ namespace Web::DOM {
GC_DEFINE_ALLOCATOR(CharacterData); GC_DEFINE_ALLOCATOR(CharacterData);
CharacterData::CharacterData(Document& document, NodeType type, String const& data) CharacterData::CharacterData(Document& document, NodeType type, Utf16String data)
: Node(document, type) : Node(document, type)
, m_data(data) , m_data(move(data))
{ {
} }
@ -32,7 +32,7 @@ void CharacterData::initialize(JS::Realm& realm)
} }
// https://dom.spec.whatwg.org/#dom-characterdata-data // https://dom.spec.whatwg.org/#dom-characterdata-data
void CharacterData::set_data(String const& data) void CharacterData::set_data(Utf16String const& data)
{ {
// [The data] setter must replace data with node this, offset 0, count thiss length, and data new value. // [The data] setter must replace data with node this, offset 0, count thiss length, and data new value.
// NOTE: Since the offset is 0, it can never be above data's length, so this can never throw. // NOTE: Since the offset is 0, it can never be above data's length, so this can never throw.
@ -42,12 +42,10 @@ void CharacterData::set_data(String const& data)
} }
// https://dom.spec.whatwg.org/#concept-cd-substring // https://dom.spec.whatwg.org/#concept-cd-substring
WebIDL::ExceptionOr<String> CharacterData::substring_data(size_t offset, size_t count) const WebIDL::ExceptionOr<Utf16String> CharacterData::substring_data(size_t offset, size_t count) const
{ {
// 1. Let length be nodes length. // 1. Let length be nodes length.
// FIXME: This is very inefficient! auto length = m_data.length_in_code_units();
auto utf16_string = Utf16String::from_utf8(m_data);
auto length = utf16_string.length_in_code_units();
// 2. If offset is greater than length, then throw an "IndexSizeError" DOMException. // 2. If offset is greater than length, then throw an "IndexSizeError" DOMException.
if (offset > length) if (offset > length)
@ -56,19 +54,17 @@ WebIDL::ExceptionOr<String> CharacterData::substring_data(size_t offset, size_t
// 3. If offset plus count is greater than length, return a string whose value is the code units from the offsetth code unit // 3. If offset plus count is greater than length, return a string whose value is the code units from the offsetth code unit
// to the end of nodes data, and then return. // to the end of nodes data, and then return.
if (offset + count > length) if (offset + count > length)
return MUST(utf16_string.substring_view(offset).to_utf8()); return Utf16String::from_utf16_without_validation(m_data.substring_view(offset));
// 4. Return a string whose value is the code units from the offsetth code unit to the offset+countth code unit in nodes data. // 4. Return a string whose value is the code units from the offsetth code unit to the offset+countth code unit in nodes data.
return MUST(utf16_string.substring_view(offset, count).to_utf8()); return Utf16String::from_utf16_without_validation(m_data.substring_view(offset, count));
} }
// https://dom.spec.whatwg.org/#concept-cd-replace // https://dom.spec.whatwg.org/#concept-cd-replace
WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t count, String const& data) WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t count, Utf16View const& data)
{ {
// 1. Let length be nodes length. // 1. Let length be nodes length.
// FIXME: This is very inefficient! auto length = m_data.length_in_code_units();
auto utf16_string = Utf16String::from_utf8(m_data);
auto length = utf16_string.length_in_code_units();
// 2. If offset is greater than length, then throw an "IndexSizeError" DOMException. // 2. If offset is greater than length, then throw an "IndexSizeError" DOMException.
if (offset > length) if (offset > length)
@ -81,27 +77,20 @@ WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t coun
// 5. Insert data into nodes data after offset code units. // 5. Insert data into nodes data after offset code units.
// 6. Let delete offset be offset + datas length. // 6. Let delete offset be offset + datas length.
// 7. Starting from delete offset code units, remove count code units from nodes data. // 7. Starting from delete offset code units, remove count code units from nodes data.
auto before_data = utf16_string.substring_view(0, offset); auto before_data = m_data.substring_view(0, offset);
auto inserted_data = Utf16String::from_utf8(data); auto after_data = m_data.substring_view(offset + count);
auto after_data = utf16_string.substring_view(offset + count);
StringBuilder full_data(StringBuilder::Mode::UTF16, before_data.length_in_code_units() + inserted_data.length_in_code_units() + after_data.length_in_code_units()); StringBuilder full_data(StringBuilder::Mode::UTF16, before_data.length_in_code_units() + data.length_in_code_units() + after_data.length_in_code_units());
full_data.append(before_data); full_data.append(before_data);
full_data.append(inserted_data); full_data.append(data);
full_data.append(after_data); full_data.append(after_data);
auto full_view = full_data.utf16_string_view();
bool characters_are_the_same = utf16_string == full_view;
auto old_data = m_data; auto old_data = m_data;
m_data = full_data.to_utf16_string();
// OPTIMIZATION: Skip UTF-8 encoding if the characters are the same.
if (!characters_are_the_same) {
m_data = MUST(full_view.to_utf8());
}
// 4. Queue a mutation record of "characterData" for node with null, null, nodes data, « », « », null, and null. // 4. Queue a mutation record of "characterData" for node with null, null, nodes data, « », « », null, and null.
// NOTE: We do this later so that the mutation observer may notify UI clients of this node's new value. // NOTE: We do this later so that the mutation observer may notify UI clients of this node's new value.
queue_mutation_record(MutationType::characterData, {}, {}, old_data, {}, {}, nullptr, nullptr); queue_mutation_record(MutationType::characterData, {}, {}, old_data.to_utf8_but_should_be_ported_to_utf16(), {}, {}, nullptr, nullptr);
// 8. For each live range whose start node is node and start offset is greater than offset but less than or equal to // 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. // offset plus count, set its start offset to offset.
@ -121,14 +110,14 @@ WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t coun
// start offset by datas length and decrease it by count. // start offset by datas length and decrease it by count.
for (auto* range : Range::live_ranges()) { for (auto* range : Range::live_ranges()) {
if (range->start_container() == this && range->start_offset() > (offset + count)) if (range->start_container() == this && range->start_offset() > (offset + count))
range->set_start_offset(range->start_offset() + inserted_data.length_in_code_units() - count); range->set_start_offset(range->start_offset() + data.length_in_code_units() - count);
} }
// 11. For each live range whose end node is node and end offset is greater than offset plus count, increase its end // 11. For each live range whose end node is node and end offset is greater than offset plus count, increase its end
// offset by datas length and decrease it by count. // offset by datas length and decrease it by count.
for (auto* range : Range::live_ranges()) { for (auto* range : Range::live_ranges()) {
if (range->end_container() == this && range->end_offset() > (offset + count)) if (range->end_container() == this && range->end_offset() > (offset + count))
range->set_end_offset(range->end_offset() + inserted_data.length_in_code_units() - count); range->set_end_offset(range->end_offset() + data.length_in_code_units() - count);
} }
// 12. If nodes parent is non-null, then run the children changed steps for nodes parent. // 12. If nodes parent is non-null, then run the children changed steps for nodes parent.
@ -136,7 +125,7 @@ WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t coun
parent()->children_changed(nullptr); parent()->children_changed(nullptr);
// OPTIMIZATION: If the characters are the same, we can skip the remainder of this function. // OPTIMIZATION: If the characters are the same, we can skip the remainder of this function.
if (characters_are_the_same) if (m_data == old_data)
return {}; return {};
if (auto* layout_node = this->layout_node(); layout_node && layout_node->is_text_node()) { if (auto* layout_node = this->layout_node(); layout_node && layout_node->is_text_node()) {
@ -160,14 +149,14 @@ WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t coun
} }
// https://dom.spec.whatwg.org/#dom-characterdata-appenddata // https://dom.spec.whatwg.org/#dom-characterdata-appenddata
WebIDL::ExceptionOr<void> CharacterData::append_data(String const& data) WebIDL::ExceptionOr<void> CharacterData::append_data(Utf16View const& data)
{ {
// The appendData(data) method steps are to replace data with node this, offset thiss length, count 0, and data data. // The appendData(data) method steps are to replace data with node this, offset thiss length, count 0, and data data.
return replace_data(this->length_in_utf16_code_units(), 0, data); return replace_data(this->length_in_utf16_code_units(), 0, data);
} }
// https://dom.spec.whatwg.org/#dom-characterdata-insertdata // https://dom.spec.whatwg.org/#dom-characterdata-insertdata
WebIDL::ExceptionOr<void> CharacterData::insert_data(size_t offset, String const& data) WebIDL::ExceptionOr<void> CharacterData::insert_data(size_t offset, Utf16View const& data)
{ {
// The insertData(offset, data) method steps are to replace data with node this, offset offset, count 0, and data data. // The insertData(offset, data) method steps are to replace data with node this, offset offset, count 0, and data data.
return replace_data(offset, 0, data); return replace_data(offset, 0, data);
@ -177,7 +166,7 @@ WebIDL::ExceptionOr<void> CharacterData::insert_data(size_t offset, String const
WebIDL::ExceptionOr<void> CharacterData::delete_data(size_t offset, size_t count) WebIDL::ExceptionOr<void> CharacterData::delete_data(size_t offset, size_t count)
{ {
// The deleteData(offset, count) method steps are to replace data with node this, offset offset, count count, and data the empty string. // The deleteData(offset, count) method steps are to replace data with node this, offset offset, count count, and data the empty string.
return replace_data(offset, count, String {}); return replace_data(offset, count, {});
} }
Unicode::Segmenter& CharacterData::grapheme_segmenter() const Unicode::Segmenter& CharacterData::grapheme_segmenter() const

View file

@ -6,7 +6,7 @@
#pragma once #pragma once
#include <AK/String.h> #include <AK/Utf16String.h>
#include <AK/Utf16View.h> #include <AK/Utf16View.h>
#include <LibUnicode/Forward.h> #include <LibUnicode/Forward.h>
#include <LibWeb/DOM/ChildNode.h> #include <LibWeb/DOM/ChildNode.h>
@ -26,30 +26,27 @@ class CharacterData
public: public:
virtual ~CharacterData() override; virtual ~CharacterData() override;
String const& data() const { return m_data; } Utf16String const& data() const { return m_data; }
void set_data(String const&); void set_data(Utf16String const&);
unsigned length_in_utf16_code_units() const unsigned length_in_utf16_code_units() const { return m_data.length_in_code_units(); }
{
return AK::utf16_code_unit_length_from_utf8(m_data);
}
WebIDL::ExceptionOr<String> substring_data(size_t offset_in_utf16_code_units, size_t count_in_utf16_code_units) const; WebIDL::ExceptionOr<Utf16String> substring_data(size_t offset_in_utf16_code_units, size_t count_in_utf16_code_units) const;
WebIDL::ExceptionOr<void> append_data(String const&); WebIDL::ExceptionOr<void> append_data(Utf16View const&);
WebIDL::ExceptionOr<void> insert_data(size_t offset_in_utf16_code_units, String const&); WebIDL::ExceptionOr<void> insert_data(size_t offset_in_utf16_code_units, Utf16View const&);
WebIDL::ExceptionOr<void> delete_data(size_t offset_in_utf16_code_units, size_t count_in_utf16_code_units); WebIDL::ExceptionOr<void> delete_data(size_t offset_in_utf16_code_units, size_t count_in_utf16_code_units);
WebIDL::ExceptionOr<void> replace_data(size_t offset_in_utf16_code_units, size_t count_in_utf16_code_units, String const&); WebIDL::ExceptionOr<void> replace_data(size_t offset_in_utf16_code_units, size_t count_in_utf16_code_units, Utf16View const&);
Unicode::Segmenter& grapheme_segmenter() const; Unicode::Segmenter& grapheme_segmenter() const;
Unicode::Segmenter& word_segmenter() const; Unicode::Segmenter& word_segmenter() const;
protected: protected:
CharacterData(Document&, NodeType, String const&); CharacterData(Document&, NodeType, Utf16String);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
private: private:
String m_data; Utf16String m_data;
mutable OwnPtr<Unicode::Segmenter> m_grapheme_segmenter; mutable OwnPtr<Unicode::Segmenter> m_grapheme_segmenter;
mutable OwnPtr<Unicode::Segmenter> m_word_segmenter; mutable OwnPtr<Unicode::Segmenter> m_word_segmenter;

View file

@ -5,14 +5,14 @@
// https://dom.spec.whatwg.org/#characterdata // https://dom.spec.whatwg.org/#characterdata
[Exposed=Window] [Exposed=Window]
interface CharacterData : Node { interface CharacterData : Node {
[LegacyNullToEmptyString] attribute DOMString data; [LegacyNullToEmptyString] attribute Utf16DOMString data;
[ImplementedAs=length_in_utf16_code_units] readonly attribute unsigned long length; [ImplementedAs=length_in_utf16_code_units] readonly attribute unsigned long length;
DOMString substringData(unsigned long offset, unsigned long count); Utf16DOMString substringData(unsigned long offset, unsigned long count);
undefined appendData(DOMString data); undefined appendData(Utf16DOMString data);
undefined insertData(unsigned long offset, DOMString data); undefined insertData(unsigned long offset, Utf16DOMString data);
undefined deleteData(unsigned long offset, unsigned long count); undefined deleteData(unsigned long offset, unsigned long count);
undefined replaceData(unsigned long offset, unsigned long count, DOMString data); undefined replaceData(unsigned long offset, unsigned long count, Utf16DOMString data);
// https://dom.spec.whatwg.org/#interface-nondocumenttypechildnode // https://dom.spec.whatwg.org/#interface-nondocumenttypechildnode
readonly attribute Element? previousElementSibling; readonly attribute Element? previousElementSibling;

View file

@ -16,7 +16,7 @@ template<typename NodeType>
class ChildNode { class ChildNode {
public: public:
// https://dom.spec.whatwg.org/#dom-childnode-before // https://dom.spec.whatwg.org/#dom-childnode-before
WebIDL::ExceptionOr<void> before(Vector<Variant<GC::Root<Node>, String>> const& nodes) WebIDL::ExceptionOr<void> before(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes)
{ {
auto* node = static_cast<NodeType*>(this); auto* node = static_cast<NodeType*>(this);
@ -46,7 +46,7 @@ public:
} }
// https://dom.spec.whatwg.org/#dom-childnode-after // https://dom.spec.whatwg.org/#dom-childnode-after
WebIDL::ExceptionOr<void> after(Vector<Variant<GC::Root<Node>, String>> const& nodes) WebIDL::ExceptionOr<void> after(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes)
{ {
auto* node = static_cast<NodeType*>(this); auto* node = static_cast<NodeType*>(this);
@ -70,7 +70,7 @@ public:
} }
// https://dom.spec.whatwg.org/#dom-childnode-replacewith // https://dom.spec.whatwg.org/#dom-childnode-replacewith
WebIDL::ExceptionOr<void> replace_with(Vector<Variant<GC::Root<Node>, String>> const& nodes) WebIDL::ExceptionOr<void> replace_with(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes)
{ {
auto* node = static_cast<NodeType*>(this); auto* node = static_cast<NodeType*>(this);
@ -117,7 +117,7 @@ protected:
ChildNode() = default; ChildNode() = default;
private: private:
GC::Ptr<Node> viable_previous_sibling_for_insertion(Vector<Variant<GC::Root<Node>, String>> const& nodes) GC::Ptr<Node> viable_previous_sibling_for_insertion(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes)
{ {
auto* node = static_cast<NodeType*>(this); auto* node = static_cast<NodeType*>(this);
@ -142,7 +142,7 @@ private:
return nullptr; return nullptr;
} }
GC::Ptr<Node> viable_next_sibling_for_insertion(Vector<Variant<GC::Root<Node>, String>> const& nodes) GC::Ptr<Node> viable_next_sibling_for_insertion(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes)
{ {
auto* node = static_cast<NodeType*>(this); auto* node = static_cast<NodeType*>(this);

View file

@ -3,8 +3,8 @@
// https://dom.spec.whatwg.org/#childnode // https://dom.spec.whatwg.org/#childnode
interface mixin ChildNode { interface mixin ChildNode {
[CEReactions, Unscopable] undefined before((Node or DOMString)... nodes); [CEReactions, Unscopable] undefined before((Node or Utf16DOMString)... nodes);
[CEReactions, Unscopable] undefined after((Node or DOMString)... nodes); [CEReactions, Unscopable] undefined after((Node or Utf16DOMString)... nodes);
[CEReactions, Unscopable] undefined replaceWith((Node or DOMString)... nodes); [CEReactions, Unscopable] undefined replaceWith((Node or Utf16DOMString)... nodes);
[CEReactions, Unscopable, ImplementedAs=remove_binding] undefined remove(); [CEReactions, Unscopable, ImplementedAs=remove_binding] undefined remove();
}; };

View file

@ -13,16 +13,16 @@ namespace Web::DOM {
GC_DEFINE_ALLOCATOR(Comment); GC_DEFINE_ALLOCATOR(Comment);
Comment::Comment(Document& document, String const& data) Comment::Comment(Document& document, Utf16String data)
: CharacterData(document, NodeType::COMMENT_NODE, data) : CharacterData(document, NodeType::COMMENT_NODE, move(data))
{ {
} }
// https://dom.spec.whatwg.org/#dom-comment-comment // https://dom.spec.whatwg.org/#dom-comment-comment
WebIDL::ExceptionOr<GC::Ref<Comment>> Comment::construct_impl(JS::Realm& realm, String const& data) WebIDL::ExceptionOr<GC::Ref<Comment>> Comment::construct_impl(JS::Realm& realm, Utf16String data)
{ {
auto& window = as<HTML::Window>(realm.global_object()); auto& window = as<HTML::Window>(realm.global_object());
return realm.create<Comment>(window.associated_document(), data); return realm.create<Comment>(window.associated_document(), move(data));
} }
void Comment::initialize(JS::Realm& realm) void Comment::initialize(JS::Realm& realm)

View file

@ -15,13 +15,13 @@ class Comment final : public CharacterData {
GC_DECLARE_ALLOCATOR(Comment); GC_DECLARE_ALLOCATOR(Comment);
public: public:
static WebIDL::ExceptionOr<GC::Ref<Comment>> construct_impl(JS::Realm&, String const& data); static WebIDL::ExceptionOr<GC::Ref<Comment>> construct_impl(JS::Realm&, Utf16String data);
virtual ~Comment() override = default; virtual ~Comment() override = default;
virtual FlyString node_name() const override { return "#comment"_fly_string; } virtual FlyString node_name() const override { return "#comment"_fly_string; }
private: private:
Comment(Document&, String const&); Comment(Document&, Utf16String);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
}; };

View file

@ -3,5 +3,5 @@
// https://dom.spec.whatwg.org/#comment // https://dom.spec.whatwg.org/#comment
[Exposed=Window] [Exposed=Window]
interface Comment : CharacterData { interface Comment : CharacterData {
constructor(optional DOMString data = ""); constructor(optional Utf16DOMString data = "");
}; };

View file

@ -90,7 +90,7 @@ WebIDL::ExceptionOr<GC::Ref<XMLDocument>> DOMImplementation::create_document(Opt
} }
// https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument // https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument
GC::Ref<Document> DOMImplementation::create_html_document(Optional<String> const& title) const GC::Ref<Document> DOMImplementation::create_html_document(Optional<Utf16String> const& title) const
{ {
// 1. Let doc be a new document that is an HTML document. // 1. Let doc be a new document that is an HTML document.
auto html_document = HTML::HTMLDocument::create(realm()); auto html_document = HTML::HTMLDocument::create(realm());

View file

@ -22,7 +22,7 @@ public:
virtual ~DOMImplementation(); virtual ~DOMImplementation();
WebIDL::ExceptionOr<GC::Ref<XMLDocument>> create_document(Optional<FlyString> const&, String const&, GC::Ptr<DocumentType>) const; WebIDL::ExceptionOr<GC::Ref<XMLDocument>> create_document(Optional<FlyString> const&, String const&, GC::Ptr<DocumentType>) const;
GC::Ref<Document> create_html_document(Optional<String> const& title) const; GC::Ref<Document> create_html_document(Optional<Utf16String> const& title) const;
WebIDL::ExceptionOr<GC::Ref<DocumentType>> create_document_type(String const& name, String const& public_id, String const& system_id); WebIDL::ExceptionOr<GC::Ref<DocumentType>> create_document_type(String const& name, String const& public_id, String const& system_id);
// https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature // https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature

View file

@ -6,7 +6,7 @@
interface DOMImplementation { interface DOMImplementation {
[NewObject] DocumentType createDocumentType(DOMString qualifiedName, DOMString publicId, DOMString systemId); [NewObject] DocumentType createDocumentType(DOMString qualifiedName, DOMString publicId, DOMString systemId);
[NewObject] XMLDocument createDocument([FlyString] DOMString? namespace, [LegacyNullToEmptyString] DOMString qualifiedName, optional DocumentType? doctype = null); [NewObject] XMLDocument createDocument([FlyString] DOMString? namespace, [LegacyNullToEmptyString] DOMString qualifiedName, optional DocumentType? doctype = null);
[NewObject] Document createHTMLDocument(optional DOMString title); [NewObject] Document createHTMLDocument(optional Utf16DOMString title);
boolean hasFeature(); // useless; always returns true boolean hasFeature(); // useless; always returns true
}; };

View file

@ -2122,13 +2122,13 @@ GC::Ref<DocumentFragment> Document::create_document_fragment()
return realm().create<DocumentFragment>(*this); return realm().create<DocumentFragment>(*this);
} }
GC::Ref<Text> Document::create_text_node(String const& data) GC::Ref<Text> Document::create_text_node(Utf16String data)
{ {
return realm().create<Text>(*this, data); return realm().create<Text>(*this, move(data));
} }
// https://dom.spec.whatwg.org/#dom-document-createcdatasection // https://dom.spec.whatwg.org/#dom-document-createcdatasection
WebIDL::ExceptionOr<GC::Ref<CDATASection>> Document::create_cdata_section(String const& data) WebIDL::ExceptionOr<GC::Ref<CDATASection>> Document::create_cdata_section(Utf16String data)
{ {
// 1. If this is an HTML document, then throw a "NotSupportedError" DOMException. // 1. If this is an HTML document, then throw a "NotSupportedError" DOMException.
if (is_html_document()) if (is_html_document())
@ -2139,16 +2139,16 @@ WebIDL::ExceptionOr<GC::Ref<CDATASection>> Document::create_cdata_section(String
return WebIDL::InvalidCharacterError::create(realm(), "String may not contain ']]>'"_string); return WebIDL::InvalidCharacterError::create(realm(), "String may not contain ']]>'"_string);
// 3. Return a new CDATASection node with its data set to data and node document set to this. // 3. Return a new CDATASection node with its data set to data and node document set to this.
return realm().create<CDATASection>(*this, data); return realm().create<CDATASection>(*this, move(data));
} }
GC::Ref<Comment> Document::create_comment(String const& data) GC::Ref<Comment> Document::create_comment(Utf16String data)
{ {
return realm().create<Comment>(*this, data); return realm().create<Comment>(*this, move(data));
} }
// https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction // https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction
WebIDL::ExceptionOr<GC::Ref<ProcessingInstruction>> Document::create_processing_instruction(String const& target, String const& data) WebIDL::ExceptionOr<GC::Ref<ProcessingInstruction>> Document::create_processing_instruction(String const& target, Utf16String data)
{ {
// 1. If target does not match the Name production, then throw an "InvalidCharacterError" DOMException. // 1. If target does not match the Name production, then throw an "InvalidCharacterError" DOMException.
if (!is_valid_name(target)) if (!is_valid_name(target))
@ -2159,7 +2159,7 @@ WebIDL::ExceptionOr<GC::Ref<ProcessingInstruction>> Document::create_processing_
return WebIDL::InvalidCharacterError::create(realm(), "String may not contain '?>'"_string); return WebIDL::InvalidCharacterError::create(realm(), "String may not contain '?>'"_string);
// 3. Return a new ProcessingInstruction node, with target set to target, data set to data, and node document set to this. // 3. Return a new ProcessingInstruction node, with target set to target, data set to data, and node document set to this.
return realm().create<ProcessingInstruction>(*this, data, target); return realm().create<ProcessingInstruction>(*this, move(data), target);
} }
GC::Ref<Range> Document::create_range() GC::Ref<Range> Document::create_range()

View file

@ -377,10 +377,10 @@ public:
WebIDL::ExceptionOr<GC::Ref<Element>> create_element(String const& local_name, Variant<String, ElementCreationOptions> const& options); WebIDL::ExceptionOr<GC::Ref<Element>> create_element(String const& local_name, Variant<String, ElementCreationOptions> const& options);
WebIDL::ExceptionOr<GC::Ref<Element>> create_element_ns(Optional<FlyString> const& namespace_, String const& qualified_name, Variant<String, ElementCreationOptions> const& options); WebIDL::ExceptionOr<GC::Ref<Element>> create_element_ns(Optional<FlyString> const& namespace_, String const& qualified_name, Variant<String, ElementCreationOptions> const& options);
GC::Ref<DocumentFragment> create_document_fragment(); GC::Ref<DocumentFragment> create_document_fragment();
GC::Ref<Text> create_text_node(String const& data); GC::Ref<Text> create_text_node(Utf16String data);
WebIDL::ExceptionOr<GC::Ref<CDATASection>> create_cdata_section(String const& data); WebIDL::ExceptionOr<GC::Ref<CDATASection>> create_cdata_section(Utf16String data);
GC::Ref<Comment> create_comment(String const& data); GC::Ref<Comment> create_comment(Utf16String data);
WebIDL::ExceptionOr<GC::Ref<ProcessingInstruction>> create_processing_instruction(String const& target, String const& data); WebIDL::ExceptionOr<GC::Ref<ProcessingInstruction>> create_processing_instruction(String const& target, Utf16String data);
WebIDL::ExceptionOr<GC::Ref<Attr>> create_attribute(String const& local_name); WebIDL::ExceptionOr<GC::Ref<Attr>> create_attribute(String const& local_name);
WebIDL::ExceptionOr<GC::Ref<Attr>> create_attribute_ns(Optional<FlyString> const& namespace_, String const& qualified_name); WebIDL::ExceptionOr<GC::Ref<Attr>> create_attribute_ns(Optional<FlyString> const& namespace_, String const& qualified_name);

View file

@ -98,10 +98,10 @@ interface Document : Node {
[CEReactions, NewObject] Element createElement(DOMString tagName, optional (DOMString or ElementCreationOptions) options = {}); [CEReactions, NewObject] Element createElement(DOMString tagName, optional (DOMString or ElementCreationOptions) options = {});
[CEReactions, NewObject] Element createElementNS([FlyString] DOMString? namespace, DOMString qualifiedName, optional (DOMString or ElementCreationOptions) options = {}); [CEReactions, NewObject] Element createElementNS([FlyString] DOMString? namespace, DOMString qualifiedName, optional (DOMString or ElementCreationOptions) options = {});
DocumentFragment createDocumentFragment(); DocumentFragment createDocumentFragment();
Text createTextNode(DOMString data); Text createTextNode(Utf16DOMString data);
[NewObject] CDATASection createCDATASection(DOMString data); [NewObject] CDATASection createCDATASection(Utf16DOMString data);
Comment createComment(DOMString data); Comment createComment(Utf16DOMString data);
[NewObject] ProcessingInstruction createProcessingInstruction(DOMString target, DOMString data); [NewObject] ProcessingInstruction createProcessingInstruction(DOMString target, Utf16DOMString data);
[NewObject] Attr createAttribute(DOMString localName); [NewObject] Attr createAttribute(DOMString localName);
[NewObject] Attr createAttributeNS([FlyString] DOMString? namespace, DOMString qualifiedName); [NewObject] Attr createAttributeNS([FlyString] DOMString? namespace, DOMString qualifiedName);

View file

@ -26,12 +26,12 @@
namespace Web { namespace Web {
// Replaces a document's content with a simple error message. // Replaces a document's content with a simple error message.
static void convert_to_xml_error_document(DOM::Document& document, String error_string) static void convert_to_xml_error_document(DOM::Document& document, Utf16String error_string)
{ {
auto html_element = MUST(DOM::create_element(document, HTML::TagNames::html, Namespace::HTML)); auto html_element = MUST(DOM::create_element(document, HTML::TagNames::html, Namespace::HTML));
auto body_element = MUST(DOM::create_element(document, HTML::TagNames::body, Namespace::HTML)); auto body_element = MUST(DOM::create_element(document, HTML::TagNames::body, Namespace::HTML));
MUST(html_element->append_child(body_element)); MUST(html_element->append_child(body_element));
MUST(body_element->append_child(document.realm().create<DOM::Text>(document, error_string))); MUST(body_element->append_child(document.realm().create<DOM::Text>(document, move(error_string))));
document.remove_all_children(); document.remove_all_children();
MUST(document.append_child(html_element)); MUST(document.append_child(html_element));
} }
@ -50,7 +50,7 @@ bool build_xml_document(DOM::Document& document, ByteBuffer const& data, Optiona
VERIFY(decoder.has_value()); VERIFY(decoder.has_value());
// Well-formed XML documents contain only properly encoded characters // Well-formed XML documents contain only properly encoded characters
if (!decoder->validate(data)) { if (!decoder->validate(data)) {
convert_to_xml_error_document(document, "XML Document contains improperly-encoded characters"_string); convert_to_xml_error_document(document, "XML Document contains improperly-encoded characters"_utf16);
return false; return false;
} }
auto source = decoder->to_utf8(data).release_value_but_fixme_should_propagate_errors(); auto source = decoder->to_utf8(data).release_value_but_fixme_should_propagate_errors();
@ -162,7 +162,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
if (!decoder->validate(data)) { if (!decoder->validate(data)) {
// FIXME: Insert error message into the document. // FIXME: Insert error message into the document.
dbgln("XML Document contains improperly-encoded characters"); dbgln("XML Document contains improperly-encoded characters");
convert_to_xml_error_document(document, "XML Document contains improperly-encoded characters"_string); convert_to_xml_error_document(document, "XML Document contains improperly-encoded characters"_utf16);
// NOTE: This ensures that the `load` event gets fired for the frame loading this document. // NOTE: This ensures that the `load` event gets fired for the frame loading this document.
document->completely_finish_loading(); document->completely_finish_loading();
@ -172,7 +172,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
if (source.is_error()) { if (source.is_error()) {
// FIXME: Insert error message into the document. // FIXME: Insert error message into the document.
dbgln("Failed to decode XML document: {}", source.error()); dbgln("Failed to decode XML document: {}", source.error());
convert_to_xml_error_document(document, MUST(String::formatted("Failed to decode XML document: {}", source.error()))); convert_to_xml_error_document(document, Utf16String::formatted("Failed to decode XML document: {}", source.error()));
// NOTE: This ensures that the `load` event gets fired for the frame loading this document. // NOTE: This ensures that the `load` event gets fired for the frame loading this document.
document->completely_finish_loading(); document->completely_finish_loading();
@ -184,7 +184,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
if (result.is_error()) { if (result.is_error()) {
// FIXME: Insert error message into the document. // FIXME: Insert error message into the document.
dbgln("Failed to parse XML document: {}", result.error()); dbgln("Failed to parse XML document: {}", result.error());
convert_to_xml_error_document(document, MUST(String::formatted("Failed to parse XML document: {}", result.error()))); convert_to_xml_error_document(document, Utf16String::formatted("Failed to parse XML document: {}", result.error()));
// NOTE: XMLDocumentBuilder ensures that the `load` event gets fired. We don't need to do anything else here. // NOTE: XMLDocumentBuilder ensures that the `load` event gets fired. We don't need to do anything else here.
} }
@ -246,10 +246,10 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_text_document(HTML::Navi
// 5. User agents may add content to the head element of document, e.g., linking to a style sheet, providing // 5. User agents may add content to the head element of document, e.g., linking to a style sheet, providing
// script, or giving the document a title. // script, or giving the document a title.
auto title = MUST(String::from_byte_string(LexicalPath::basename(url.to_byte_string()))); auto title = Utf16String::from_utf8_with_replacement_character(LexicalPath::basename(url.to_byte_string()));
auto title_element = MUST(DOM::create_element(document, HTML::TagNames::title, Namespace::HTML)); auto title_element = MUST(DOM::create_element(document, HTML::TagNames::title, Namespace::HTML));
MUST(document->head()->append_child(title_element)); MUST(document->head()->append_child(title_element));
auto title_text = document->realm().create<DOM::Text>(document, title); auto title_text = document->realm().create<DOM::Text>(document, move(title));
MUST(title_element->append_child(*title_text)); MUST(title_element->append_child(*title_text));
}); });
@ -283,11 +283,13 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_media_document(HTML::Nav
// video, or audio resource. // video, or audio resource.
// 6. User agents may add content to the head element of document, or attributes to host element, e.g., to link // 6. User agents may add content to the head element of document, or attributes to host element, e.g., to link
// to a style sheet, to provide a script, to give the document a title, or to make the media autoplay. // to a style sheet, to provide a script, to give the document a title, or to make the media autoplay.
auto insert_title = [](auto& document, auto title) -> WebIDL::ExceptionOr<void> { auto insert_title = [](auto& document, auto const& document_url) -> WebIDL::ExceptionOr<void> {
auto title = Utf16String::from_utf8_with_replacement_character(LexicalPath::basename(document_url.to_byte_string()));
auto title_element = TRY(DOM::create_element(document, HTML::TagNames::title, Namespace::HTML)); auto title_element = TRY(DOM::create_element(document, HTML::TagNames::title, Namespace::HTML));
TRY(document->head()->append_child(title_element)); TRY(document->head()->append_child(title_element));
auto title_text = document->realm().template create<DOM::Text>(document, title); auto title_text = document->realm().template create<DOM::Text>(document, move(title));
TRY(title_element->append_child(*title_text)); TRY(title_element->append_child(*title_text));
return {}; return {};
}; };
@ -315,7 +317,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_media_document(HTML::Nav
auto img_element = TRY(DOM::create_element(document, HTML::TagNames::img, Namespace::HTML)); auto img_element = TRY(DOM::create_element(document, HTML::TagNames::img, Namespace::HTML));
TRY(img_element->set_attribute(HTML::AttributeNames::src, url_string)); TRY(img_element->set_attribute(HTML::AttributeNames::src, url_string));
TRY(document->body()->append_child(img_element)); TRY(document->body()->append_child(img_element));
TRY(insert_title(document, MUST(String::from_byte_string(LexicalPath::basename(url_string.to_byte_string()))))); TRY(insert_title(document, url_string));
} else if (type.type() == "video"sv) { } else if (type.type() == "video"sv) {
auto video_element = TRY(DOM::create_element(document, HTML::TagNames::video, Namespace::HTML)); auto video_element = TRY(DOM::create_element(document, HTML::TagNames::video, Namespace::HTML));
@ -323,7 +325,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_media_document(HTML::Nav
TRY(video_element->set_attribute(HTML::AttributeNames::autoplay, String {})); TRY(video_element->set_attribute(HTML::AttributeNames::autoplay, String {}));
TRY(video_element->set_attribute(HTML::AttributeNames::controls, String {})); TRY(video_element->set_attribute(HTML::AttributeNames::controls, String {}));
TRY(document->body()->append_child(video_element)); TRY(document->body()->append_child(video_element));
TRY(insert_title(document, MUST(String::from_byte_string(LexicalPath::basename(url_string.to_byte_string()))))); TRY(insert_title(document, url_string));
} else if (type.type() == "audio"sv) { } else if (type.type() == "audio"sv) {
auto audio_element = TRY(DOM::create_element(document, HTML::TagNames::audio, Namespace::HTML)); auto audio_element = TRY(DOM::create_element(document, HTML::TagNames::audio, Namespace::HTML));
@ -331,7 +333,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_media_document(HTML::Nav
TRY(audio_element->set_attribute(HTML::AttributeNames::autoplay, String {})); TRY(audio_element->set_attribute(HTML::AttributeNames::autoplay, String {}));
TRY(audio_element->set_attribute(HTML::AttributeNames::controls, String {})); TRY(audio_element->set_attribute(HTML::AttributeNames::controls, String {}));
TRY(document->body()->append_child(audio_element)); TRY(document->body()->append_child(audio_element));
TRY(insert_title(document, MUST(String::from_byte_string(LexicalPath::basename(url_string.to_byte_string()))))); TRY(insert_title(document, url_string));
} else { } else {
// FIXME: According to https://mimesniff.spec.whatwg.org/#audio-or-video-mime-type we might have to deal with // FIXME: According to https://mimesniff.spec.whatwg.org/#audio-or-video-mime-type we might have to deal with

View file

@ -2200,7 +2200,7 @@ WebIDL::ExceptionOr<GC::Ptr<Element>> Element::insert_adjacent_element(String co
} }
// https://dom.spec.whatwg.org/#dom-element-insertadjacenttext // https://dom.spec.whatwg.org/#dom-element-insertadjacenttext
WebIDL::ExceptionOr<void> Element::insert_adjacent_text(String const& where, String const& data) WebIDL::ExceptionOr<void> Element::insert_adjacent_text(String const& where, Utf16String const& data)
{ {
// 1. Let text be a new Text node whose data is data and node document is thiss node document. // 1. Let text be a new Text node whose data is data and node document is thiss node document.
auto text = realm().create<DOM::Text>(document(), data); auto text = realm().create<DOM::Text>(document(), data);

View file

@ -306,7 +306,7 @@ public:
bool is_actually_disabled() const; bool is_actually_disabled() const;
WebIDL::ExceptionOr<GC::Ptr<Element>> insert_adjacent_element(String const& where, GC::Ref<Element> element); WebIDL::ExceptionOr<GC::Ptr<Element>> insert_adjacent_element(String const& where, GC::Ref<Element> element);
WebIDL::ExceptionOr<void> insert_adjacent_text(String const& where, String const& data); WebIDL::ExceptionOr<void> insert_adjacent_text(String const& where, Utf16String const& data);
// https://w3c.github.io/csswg-drafts/cssom-view-1/#dom-element-scrollintoview // https://w3c.github.io/csswg-drafts/cssom-view-1/#dom-element-scrollintoview
ErrorOr<void> scroll_into_view(Optional<Variant<bool, ScrollIntoViewOptions>> = {}); ErrorOr<void> scroll_into_view(Optional<Variant<bool, ScrollIntoViewOptions>> = {});

View file

@ -77,7 +77,7 @@ interface Element : Node {
HTMLCollection getElementsByClassName(DOMString className); HTMLCollection getElementsByClassName(DOMString className);
[CEReactions] Element? insertAdjacentElement(DOMString where, Element element); // legacy [CEReactions] Element? insertAdjacentElement(DOMString where, Element element); // legacy
undefined insertAdjacentText(DOMString where, DOMString data); // legacy undefined insertAdjacentText(DOMString where, Utf16DOMString data); // legacy
// https://dom.spec.whatwg.org/#interface-nondocumenttypechildnode // https://dom.spec.whatwg.org/#interface-nondocumenttypechildnode
readonly attribute Element? nextElementSibling; readonly attribute Element? nextElementSibling;

View file

@ -186,7 +186,7 @@ Optional<String> Node::text_content() const
// If CharacterData, return thiss data. // If CharacterData, return thiss data.
if (is<CharacterData>(this)) if (is<CharacterData>(this))
return static_cast<CharacterData const&>(*this).data(); return static_cast<CharacterData const&>(*this).data().to_utf8_but_should_be_ported_to_utf16();
// If Attr node, return this's value. // If Attr node, return this's value.
if (is<Attr>(*this)) if (is<Attr>(*this))
@ -214,9 +214,8 @@ void Node::set_text_content(Optional<String> const& maybe_content)
// If CharacterData, replace data with node this, offset 0, count thiss length, and data the given value. // If CharacterData, replace data with node this, offset 0, count thiss length, and data the given value.
else if (is<CharacterData>(this)) { else if (is<CharacterData>(this)) {
auto* character_data_node = as<CharacterData>(this); auto* character_data_node = as<CharacterData>(this);
character_data_node->set_data(content); character_data_node->set_data(Utf16String::from_utf8(content));
// FIXME: CharacterData::set_data is not spec compliant. Make this match the spec when set_data becomes spec compliant. // FIXME: CharacterData::set_data is not spec compliant. Make this match the spec when set_data becomes spec compliant.
// Do note that this will make this function able to throw an exception. // Do note that this will make this function able to throw an exception.
@ -288,12 +287,12 @@ WebIDL::ExceptionOr<void> Node::normalize()
} }
// 3. Let data be the concatenation of the data of nodes contiguous exclusive Text nodes (excluding itself), in tree order. // 3. Let data be the concatenation of the data of nodes contiguous exclusive Text nodes (excluding itself), in tree order.
StringBuilder data; StringBuilder data(StringBuilder::Mode::UTF16);
for (auto const& text_node : contiguous_exclusive_text_nodes_excluding_self(node)) for (auto const& text_node : contiguous_exclusive_text_nodes_excluding_self(node))
data.append(text_node->data()); data.append(text_node->data());
// 4. Replace data with node node, offset length, count 0, and data data. // 4. Replace data with node node, offset length, count 0, and data data.
TRY(character_data.replace_data(length, 0, MUST(data.to_string()))); TRY(character_data.replace_data(length, 0, data.to_utf16_string()));
// 5. Let currentNode be nodes next sibling. // 5. Let currentNode be nodes next sibling.
auto* current_node = node.next_sibling(); auto* current_node = node.next_sibling();
@ -363,7 +362,7 @@ Optional<String> Node::node_value() const
// If CharacterData, return thiss data. // If CharacterData, return thiss data.
if (is<CharacterData>(this)) { if (is<CharacterData>(this)) {
return as<CharacterData>(this)->data(); return as<CharacterData>(this)->data().to_utf8_but_should_be_ported_to_utf16();
} }
// Otherwise, return null. // Otherwise, return null.
@ -382,7 +381,7 @@ void Node::set_node_value(Optional<String> const& maybe_value)
as<Attr>(this)->set_value(move(value)); as<Attr>(this)->set_value(move(value));
} else if (is<CharacterData>(this)) { } else if (is<CharacterData>(this)) {
// If CharacterData, replace data with node this, offset 0, count thiss length, and data the given value. // If CharacterData, replace data with node this, offset 0, count thiss length, and data the given value.
as<CharacterData>(this)->set_data(value); as<CharacterData>(this)->set_data(Utf16String::from_utf8(value));
} }
// Otherwise, do nothing. // Otherwise, do nothing.
@ -1870,7 +1869,7 @@ bool Node::is_uninteresting_whitespace_node() const
{ {
if (!is<Text>(*this)) if (!is<Text>(*this))
return false; return false;
if (!static_cast<Text const&>(*this).data().bytes_as_string_view().is_whitespace()) if (!static_cast<Text const&>(*this).data().is_ascii_whitespace())
return false; return false;
if (!layout_node()) if (!layout_node())
return true; return true;
@ -1936,10 +1935,10 @@ void Node::serialize_tree_as_json(JsonObjectSerializer<StringBuilder>& object) c
MUST(object.add("type"sv, "text")); MUST(object.add("type"sv, "text"));
auto text_node = static_cast<DOM::Text const*>(this); auto text_node = static_cast<DOM::Text const*>(this);
MUST(object.add("text"sv, text_node->data())); MUST(object.add("text"sv, text_node->data().to_utf8()));
} else if (is_comment()) { } else if (is_comment()) {
MUST(object.add("type"sv, "comment"sv)); MUST(object.add("type"sv, "comment"sv));
MUST(object.add("data"sv, static_cast<DOM::Comment const&>(*this).data())); MUST(object.add("data"sv, static_cast<DOM::Comment const&>(*this).data().to_utf8()));
} else if (is_shadow_root()) { } else if (is_shadow_root()) {
MUST(object.add("type"sv, "shadow-root")); MUST(object.add("type"sv, "shadow-root"));
MUST(object.add("mode"sv, static_cast<DOM::ShadowRoot const&>(*this).mode() == Bindings::ShadowRootMode::Open ? "open"sv : "closed"sv)); MUST(object.add("mode"sv, static_cast<DOM::ShadowRoot const&>(*this).mode() == Bindings::ShadowRootMode::Open ? "open"sv : "closed"sv));
@ -2053,7 +2052,7 @@ void Node::string_replace_all(String const& string)
// 2. If string is not the empty string, then set node to a new Text node whose data is string and node document is parents node document. // 2. If string is not the empty string, then set node to a new Text node whose data is string and node document is parents node document.
if (!string.is_empty()) if (!string.is_empty())
node = realm().create<Text>(document(), string); node = realm().create<Text>(document(), Utf16String::from_utf8(string));
// 3. Replace all with node within parent. // 3. Replace all with node within parent.
replace_all(node); replace_all(node);

View file

@ -5,7 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/ByteString.h> #include <AK/Utf16String.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentFragment.h> #include <LibWeb/DOM/DocumentFragment.h>
@ -15,7 +15,7 @@
namespace Web::DOM { namespace Web::DOM {
// https://dom.spec.whatwg.org/#converting-nodes-into-a-node // https://dom.spec.whatwg.org/#converting-nodes-into-a-node
WebIDL::ExceptionOr<GC::Ref<Node>> convert_nodes_to_single_node(Vector<Variant<GC::Root<Node>, String>> const& nodes, DOM::Document& document) WebIDL::ExceptionOr<GC::Ref<Node>> convert_nodes_to_single_node(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes, DOM::Document& document)
{ {
// 1. Let node be null. // 1. Let node be null.
// 2. Replace each string in nodes with a new Text node whose data is the string and node document is document. // 2. Replace each string in nodes with a new Text node whose data is the string and node document is document.
@ -23,11 +23,11 @@ WebIDL::ExceptionOr<GC::Ref<Node>> convert_nodes_to_single_node(Vector<Variant<G
// 4. Otherwise, set node to a new DocumentFragment node whose node document is document, and then append each node in nodes, if any, to it. // 4. Otherwise, set node to a new DocumentFragment node whose node document is document, and then append each node in nodes, if any, to it.
// 5. Return node. // 5. Return node.
auto potentially_convert_string_to_text_node = [&document](Variant<GC::Root<Node>, String> const& node) -> GC::Ref<Node> { auto potentially_convert_string_to_text_node = [&document](Variant<GC::Root<Node>, Utf16String> const& node) -> GC::Ref<Node> {
if (node.has<GC::Root<Node>>()) if (node.has<GC::Root<Node>>())
return *node.get<GC::Root<Node>>(); return *node.get<GC::Root<Node>>();
return document.realm().create<DOM::Text>(document, node.get<String>()); return document.realm().create<DOM::Text>(document, node.get<Utf16String>());
}; };
if (nodes.size() == 1) if (nodes.size() == 1)

View file

@ -13,6 +13,6 @@
namespace Web::DOM { namespace Web::DOM {
WebIDL::ExceptionOr<GC::Ref<Node>> convert_nodes_to_single_node(Vector<Variant<GC::Root<Node>, String>> const& nodes, DOM::Document& document); WebIDL::ExceptionOr<GC::Ref<Node>> convert_nodes_to_single_node(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes, DOM::Document& document);
} }

View file

@ -209,7 +209,7 @@ GC::Ref<HTMLCollection> ParentNode::get_elements_by_tag_name_ns(Optional<FlyStri
} }
// https://dom.spec.whatwg.org/#dom-parentnode-prepend // https://dom.spec.whatwg.org/#dom-parentnode-prepend
WebIDL::ExceptionOr<void> ParentNode::prepend(Vector<Variant<GC::Root<Node>, String>> const& nodes) WebIDL::ExceptionOr<void> ParentNode::prepend(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes)
{ {
// 1. Let node be the result of converting nodes into a node given nodes and thiss node document. // 1. Let node be the result of converting nodes into a node given nodes and thiss node document.
auto node = TRY(convert_nodes_to_single_node(nodes, document())); auto node = TRY(convert_nodes_to_single_node(nodes, document()));
@ -220,7 +220,7 @@ WebIDL::ExceptionOr<void> ParentNode::prepend(Vector<Variant<GC::Root<Node>, Str
return {}; return {};
} }
WebIDL::ExceptionOr<void> ParentNode::append(Vector<Variant<GC::Root<Node>, String>> const& nodes) WebIDL::ExceptionOr<void> ParentNode::append(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes)
{ {
// 1. Let node be the result of converting nodes into a node given nodes and thiss node document. // 1. Let node be the result of converting nodes into a node given nodes and thiss node document.
auto node = TRY(convert_nodes_to_single_node(nodes, document())); auto node = TRY(convert_nodes_to_single_node(nodes, document()));
@ -231,7 +231,7 @@ WebIDL::ExceptionOr<void> ParentNode::append(Vector<Variant<GC::Root<Node>, Stri
return {}; return {};
} }
WebIDL::ExceptionOr<void> ParentNode::replace_children(Vector<Variant<GC::Root<Node>, String>> const& nodes) WebIDL::ExceptionOr<void> ParentNode::replace_children(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes)
{ {
// 1. Let node be the result of converting nodes into a node given nodes and thiss node document. // 1. Let node be the result of converting nodes into a node given nodes and thiss node document.
auto node = TRY(convert_nodes_to_single_node(nodes, document())); auto node = TRY(convert_nodes_to_single_node(nodes, document()));

View file

@ -32,9 +32,9 @@ public:
GC::Ref<HTMLCollection> get_elements_by_tag_name(FlyString const&); GC::Ref<HTMLCollection> get_elements_by_tag_name(FlyString const&);
GC::Ref<HTMLCollection> get_elements_by_tag_name_ns(Optional<FlyString>, FlyString const&); GC::Ref<HTMLCollection> get_elements_by_tag_name_ns(Optional<FlyString>, FlyString const&);
WebIDL::ExceptionOr<void> prepend(Vector<Variant<GC::Root<Node>, String>> const& nodes); WebIDL::ExceptionOr<void> prepend(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes);
WebIDL::ExceptionOr<void> append(Vector<Variant<GC::Root<Node>, String>> const& nodes); WebIDL::ExceptionOr<void> append(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes);
WebIDL::ExceptionOr<void> replace_children(Vector<Variant<GC::Root<Node>, String>> const& nodes); WebIDL::ExceptionOr<void> replace_children(Vector<Variant<GC::Root<Node>, Utf16String>> const& nodes);
WebIDL::ExceptionOr<void> move_before(GC::Ref<Node> node, GC::Ptr<Node> child); WebIDL::ExceptionOr<void> move_before(GC::Ref<Node> node, GC::Ptr<Node> child);
GC::Ref<HTMLCollection> get_elements_by_class_name(StringView); GC::Ref<HTMLCollection> get_elements_by_class_name(StringView);

View file

@ -8,9 +8,9 @@ interface mixin ParentNode {
readonly attribute Element? lastElementChild; readonly attribute Element? lastElementChild;
readonly attribute unsigned long childElementCount; readonly attribute unsigned long childElementCount;
[CEReactions, Unscopable] undefined prepend((Node or DOMString)... nodes); [CEReactions, Unscopable] undefined prepend((Node or Utf16DOMString)... nodes);
[CEReactions, Unscopable] undefined append((Node or DOMString)... nodes); [CEReactions, Unscopable] undefined append((Node or Utf16DOMString)... nodes);
[CEReactions, Unscopable] undefined replaceChildren((Node or DOMString)... nodes); [CEReactions, Unscopable] undefined replaceChildren((Node or Utf16DOMString)... nodes);
[CEReactions] undefined moveBefore(Node node, Node? child); [CEReactions] undefined moveBefore(Node node, Node? child);

View file

@ -14,8 +14,8 @@ namespace Web::DOM {
GC_DEFINE_ALLOCATOR(ProcessingInstruction); GC_DEFINE_ALLOCATOR(ProcessingInstruction);
ProcessingInstruction::ProcessingInstruction(Document& document, String const& data, String const& target) ProcessingInstruction::ProcessingInstruction(Document& document, Utf16String data, String const& target)
: CharacterData(document, NodeType::PROCESSING_INSTRUCTION_NODE, data) : CharacterData(document, NodeType::PROCESSING_INSTRUCTION_NODE, move(data))
, m_target(target) , m_target(target)
{ {
} }

View file

@ -22,7 +22,7 @@ public:
String const& target() const { return m_target; } String const& target() const { return m_target; }
private: private:
ProcessingInstruction(Document&, String const& data, String const& target); ProcessingInstruction(Document&, Utf16String data, String const& target);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;

View file

@ -558,7 +558,7 @@ String Range::to_string() const
// then return the substring of that Text nodes data beginning at thiss start offset and ending at thiss end offset. // then return the substring of that Text nodes data beginning at thiss start offset and ending at thiss end offset.
if (start_container() == end_container() && is<Text>(*start_container())) { if (start_container() == end_container() && is<Text>(*start_container())) {
auto const& text = static_cast<Text const&>(*start_container()); auto const& text = static_cast<Text const&>(*start_container());
return MUST(text.substring_data(start_offset(), end_offset() - start_offset())); return MUST(text.substring_data(start_offset(), end_offset() - start_offset())).to_utf8_but_should_be_ported_to_utf16();
} }
// 3. If thiss start node is a Text node, then append the substring of that nodes data from thiss start offset until the end to s. // 3. If thiss start node is a Text node, then append the substring of that nodes data from thiss start offset until the end to s.
@ -621,7 +621,7 @@ WebIDL::ExceptionOr<GC::Ref<DocumentFragment>> Range::extract()
TRY(fragment->append_child(clone)); TRY(fragment->append_child(clone));
// 4. Replace data with node original start node, offset original start offset, count original end offset minus original start offset, and data the empty string. // 4. Replace data with node original start node, offset original start offset, count original end offset minus original start offset, and data the empty string.
TRY(static_cast<CharacterData&>(*original_start_node).replace_data(original_start_offset, original_end_offset - original_start_offset, String {})); TRY(static_cast<CharacterData&>(*original_start_node).replace_data(original_start_offset, original_end_offset - original_start_offset, {}));
// 5. Return fragment. // 5. Return fragment.
return fragment; return fragment;
@ -711,7 +711,7 @@ WebIDL::ExceptionOr<GC::Ref<DocumentFragment>> Range::extract()
TRY(fragment->append_child(clone)); TRY(fragment->append_child(clone));
// 4. Replace data with node original start node, offset original start offset, count original start nodes length minus original start offset, and data the empty string. // 4. Replace data with node original start node, offset original start offset, count original start nodes length minus original start offset, and data the empty string.
TRY(static_cast<CharacterData&>(*original_start_node).replace_data(original_start_offset, original_start_node->length() - original_start_offset, String {})); TRY(static_cast<CharacterData&>(*original_start_node).replace_data(original_start_offset, original_start_node->length() - original_start_offset, {}));
} }
// 16. Otherwise, if first partially contained child is not null: // 16. Otherwise, if first partially contained child is not null:
else if (first_partially_contained_child) { else if (first_partially_contained_child) {
@ -749,7 +749,7 @@ WebIDL::ExceptionOr<GC::Ref<DocumentFragment>> Range::extract()
TRY(fragment->append_child(clone)); TRY(fragment->append_child(clone));
// 4. Replace data with node original end node, offset 0, count original end offset, and data the empty string. // 4. Replace data with node original end node, offset 0, count original end offset, and data the empty string.
TRY(as<CharacterData>(*original_end_node).replace_data(0, original_end_offset, String {})); TRY(as<CharacterData>(*original_end_node).replace_data(0, original_end_offset, {}));
} }
// 19. Otherwise, if last partially contained child is not null: // 19. Otherwise, if last partially contained child is not null:
else if (last_partially_contained_child) { else if (last_partially_contained_child) {
@ -1088,7 +1088,7 @@ WebIDL::ExceptionOr<void> Range::delete_contents()
// 3. If original start node is original end node and it is a CharacterData node, then replace data with node original start node, offset original start offset, // 3. If original start node is original end node and it is a CharacterData node, then replace data with node original start node, offset original start offset,
// count original end offset minus original start offset, and data the empty string, and then return. // count original end offset minus original start offset, and data the empty string, and then return.
if (original_start_node.ptr() == original_end_node.ptr() && is<CharacterData>(*original_start_node)) { if (original_start_node.ptr() == original_end_node.ptr() && is<CharacterData>(*original_start_node)) {
TRY(static_cast<CharacterData&>(*original_start_node).replace_data(original_start_offset, original_end_offset - original_start_offset, String {})); TRY(static_cast<CharacterData&>(*original_start_node).replace_data(original_start_offset, original_end_offset - original_start_offset, {}));
return {}; return {};
} }
@ -1123,7 +1123,7 @@ WebIDL::ExceptionOr<void> Range::delete_contents()
// 7. If original start node is a CharacterData node, then replace data with node original start node, offset original start offset, count original start nodes length minus original start offset, data the empty string. // 7. If original start node is a CharacterData node, then replace data with node original start node, offset original start offset, count original start nodes length minus original start offset, data the empty string.
if (is<CharacterData>(*original_start_node)) if (is<CharacterData>(*original_start_node))
TRY(static_cast<CharacterData&>(*original_start_node).replace_data(original_start_offset, original_start_node->length() - original_start_offset, String {})); TRY(static_cast<CharacterData&>(*original_start_node).replace_data(original_start_offset, original_start_node->length() - original_start_offset, {}));
// 8. For each node in nodes to remove, in tree order, remove node. // 8. For each node in nodes to remove, in tree order, remove node.
for (auto& node : nodes_to_remove) for (auto& node : nodes_to_remove)
@ -1131,7 +1131,7 @@ WebIDL::ExceptionOr<void> Range::delete_contents()
// 9. If original end node is a CharacterData node, then replace data with node original end node, offset 0, count original end offset and data the empty string. // 9. If original end node is a CharacterData node, then replace data with node original end node, offset 0, count original end offset and data the empty string.
if (is<CharacterData>(*original_end_node)) if (is<CharacterData>(*original_end_node))
TRY(static_cast<CharacterData&>(*original_end_node).replace_data(0, original_end_offset, String {})); TRY(static_cast<CharacterData&>(*original_end_node).replace_data(0, original_end_offset, {}));
// 10. Set start and end to (new node, new offset). // 10. Set start and end to (new node, new offset).
TRY(set_start(*new_node, new_offset)); TRY(set_start(*new_node, new_offset));

View file

@ -18,13 +18,13 @@ namespace Web::DOM {
GC_DEFINE_ALLOCATOR(Text); GC_DEFINE_ALLOCATOR(Text);
Text::Text(Document& document, String const& data) Text::Text(Document& document, Utf16String data)
: CharacterData(document, NodeType::TEXT_NODE, data) : CharacterData(document, NodeType::TEXT_NODE, move(data))
{ {
} }
Text::Text(Document& document, NodeType type, String const& data) Text::Text(Document& document, NodeType type, Utf16String data)
: CharacterData(document, type, data) : CharacterData(document, type, move(data))
{ {
} }
@ -41,11 +41,11 @@ void Text::visit_edges(Cell::Visitor& visitor)
} }
// https://dom.spec.whatwg.org/#dom-text-text // https://dom.spec.whatwg.org/#dom-text-text
WebIDL::ExceptionOr<GC::Ref<Text>> Text::construct_impl(JS::Realm& realm, String const& data) WebIDL::ExceptionOr<GC::Ref<Text>> Text::construct_impl(JS::Realm& realm, Utf16String data)
{ {
// The new Text(data) constructor steps are to set thiss data to data and thiss node document to current global objects associated Document. // The new Text(data) constructor steps are to set thiss data to data and thiss node document to current global objects associated Document.
auto& window = as<HTML::Window>(HTML::current_principal_global_object()); auto& window = as<HTML::Window>(HTML::current_principal_global_object());
return realm.create<Text>(window.associated_document(), data); return realm.create<Text>(window.associated_document(), move(data));
} }
// https://dom.spec.whatwg.org/#dom-text-splittext // https://dom.spec.whatwg.org/#dom-text-splittext
@ -110,14 +110,14 @@ WebIDL::ExceptionOr<GC::Ref<Text>> Text::split_text(size_t offset)
} }
// 8. Replace data with node node, offset offset, count count, and data the empty string. // 8. Replace data with node node, offset offset, count count, and data the empty string.
TRY(replace_data(offset, count, String {})); TRY(replace_data(offset, count, {}));
// 9. Return new node. // 9. Return new node.
return new_node; return new_node;
} }
// https://dom.spec.whatwg.org/#dom-text-wholetext // https://dom.spec.whatwg.org/#dom-text-wholetext
String Text::whole_text() Utf16String Text::whole_text()
{ {
// https://dom.spec.whatwg.org/#contiguous-text-nodes // https://dom.spec.whatwg.org/#contiguous-text-nodes
// The contiguous Text nodes of a node node are node, nodes previous sibling Text node, if any, and its contiguous // The contiguous Text nodes of a node node are node, nodes previous sibling Text node, if any, and its contiguous
@ -141,11 +141,11 @@ String Text::whole_text()
current_node = current_node->next_sibling(); current_node = current_node->next_sibling();
} }
StringBuilder builder; StringBuilder builder(StringBuilder::Mode::UTF16);
for (auto const& text_node : nodes) for (auto const& text_node : nodes)
builder.append(text_node->data()); builder.append(text_node->data());
return MUST(builder.to_string()); return builder.to_utf16_string();
} }
// https://html.spec.whatwg.org/multipage/dom.html#text-node-directionality // https://html.spec.whatwg.org/multipage/dom.html#text-node-directionality
@ -154,7 +154,7 @@ Optional<Element::Directionality> Text::directionality() const
// 1. If text's data does not contain a code point whose bidirectional character type is L, AL, or R, then return null. // 1. If text's data does not contain a code point whose bidirectional character type is L, AL, or R, then return null.
// 2. Let codePoint be the first code point in text's data whose bidirectional character type is L, AL, or R. // 2. Let codePoint be the first code point in text's data whose bidirectional character type is L, AL, or R.
Optional<Unicode::BidiClass> found_character_bidi_class; Optional<Unicode::BidiClass> found_character_bidi_class;
for (auto code_point : Utf8View(data())) { for (auto code_point : data()) {
auto bidi_class = Unicode::bidirectional_class(code_point); auto bidi_class = Unicode::bidirectional_class(code_point);
if (first_is_one_of(bidi_class, Unicode::BidiClass::LeftToRight, Unicode::BidiClass::RightToLeftArabic, Unicode::BidiClass::RightToLeft)) { if (first_is_one_of(bidi_class, Unicode::BidiClass::LeftToRight, Unicode::BidiClass::RightToLeftArabic, Unicode::BidiClass::RightToLeft)) {
found_character_bidi_class = bidi_class; found_character_bidi_class = bidi_class;

View file

@ -22,7 +22,7 @@ class Text
public: public:
virtual ~Text() override = default; virtual ~Text() override = default;
static WebIDL::ExceptionOr<GC::Ref<Text>> construct_impl(JS::Realm& realm, String const& data); static WebIDL::ExceptionOr<GC::Ref<Text>> construct_impl(JS::Realm& realm, Utf16String data);
// ^Node // ^Node
virtual FlyString node_name() const override { return "#text"_fly_string; } virtual FlyString node_name() const override { return "#text"_fly_string; }
@ -31,7 +31,7 @@ public:
void set_max_length(Optional<size_t> max_length) { m_max_length = move(max_length); } void set_max_length(Optional<size_t> max_length) { m_max_length = move(max_length); }
WebIDL::ExceptionOr<GC::Ref<Text>> split_text(size_t offset); WebIDL::ExceptionOr<GC::Ref<Text>> split_text(size_t offset);
String whole_text(); Utf16String whole_text();
bool is_password_input() const { return m_is_password_input; } bool is_password_input() const { return m_is_password_input; }
void set_is_password_input(Badge<HTML::HTMLInputElement>, bool b) { m_is_password_input = b; } void set_is_password_input(Badge<HTML::HTMLInputElement>, bool b) { m_is_password_input = b; }
@ -39,8 +39,8 @@ public:
Optional<Element::Directionality> directionality() const; Optional<Element::Directionality> directionality() const;
protected: protected:
Text(Document&, String const&); Text(Document&, Utf16String);
Text(Document&, NodeType, String const&); Text(Document&, NodeType, Utf16String);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;

View file

@ -5,10 +5,10 @@
// https://dom.spec.whatwg.org/#text // https://dom.spec.whatwg.org/#text
[Exposed=Window] [Exposed=Window]
interface Text : CharacterData { interface Text : CharacterData {
constructor(optional DOMString data = ""); constructor(optional Utf16DOMString data = "");
[NewObject] Text splitText(unsigned long offset); [NewObject] Text splitText(unsigned long offset);
readonly attribute DOMString wholeText; readonly attribute Utf16DOMString wholeText;
}; };
Text includes Slottable; Text includes Slottable;

View file

@ -1367,10 +1367,10 @@ bool command_insert_linebreak_action(DOM::Document& document, Utf16View const&)
if (auto* text_node = as_if<DOM::Text>(*start_node); text_node) { if (auto* text_node = as_if<DOM::Text>(*start_node); text_node) {
auto resolved_white_space_collapse = resolved_keyword(*start_node, CSS::PropertyID::WhiteSpaceCollapse); auto resolved_white_space_collapse = resolved_keyword(*start_node, CSS::PropertyID::WhiteSpaceCollapse);
if (resolved_white_space_collapse.has_value() && first_is_one_of(resolved_white_space_collapse.value(), CSS::Keyword::Preserve, CSS::Keyword::PreserveBreaks)) { if (resolved_white_space_collapse.has_value() && first_is_one_of(resolved_white_space_collapse.value(), CSS::Keyword::Preserve, CSS::Keyword::PreserveBreaks)) {
MUST(text_node->insert_data(active_range.start_offset(), "\n"_string)); MUST(text_node->insert_data(active_range.start_offset(), "\n"_utf16));
MUST(selection.collapse(start_node, active_range.start_offset() + 1)); MUST(selection.collapse(start_node, active_range.start_offset() + 1));
if (selection.range()->start_offset() == start_node->length()) if (selection.range()->start_offset() == start_node->length())
MUST(text_node->insert_data(active_range.start_offset(), "\n"_string)); MUST(text_node->insert_data(active_range.start_offset(), "\n"_utf16));
return true; return true;
} }
} }
@ -1796,7 +1796,7 @@ bool command_insert_text_action(DOM::Document& document, Utf16View const& value)
// 13. If node is a Text node: // 13. If node is a Text node:
if (is<DOM::Text>(*node)) { if (is<DOM::Text>(*node)) {
// 1. Call insertData(offset, value) on node. // 1. Call insertData(offset, value) on node.
MUST(static_cast<DOM::Text&>(*node).insert_data(offset, value.to_utf8_but_should_be_ported_to_utf16())); MUST(static_cast<DOM::Text&>(*node).insert_data(offset, value));
// 2. Call collapse(node, offset) on the context object's selection. // 2. Call collapse(node, offset) on the context object's selection.
MUST(selection.collapse(node, offset)); MUST(selection.collapse(node, offset));
@ -1812,7 +1812,7 @@ bool command_insert_text_action(DOM::Document& document, Utf16View const& value)
node->first_child()->remove(); node->first_child()->remove();
// 2. Let text be the result of calling createTextNode(value) on the context object. // 2. Let text be the result of calling createTextNode(value) on the context object.
auto text = document.create_text_node(value.to_utf8_but_should_be_ported_to_utf16()); auto text = document.create_text_node(Utf16String::from_utf16_without_validation(value));
// 3. Call insertNode(text) on the active range. // 3. Call insertNode(text) on the active range.
MUST(active_range(document)->insert_node(text)); MUST(active_range(document)->insert_node(text));

View file

@ -566,7 +566,7 @@ void canonicalize_whitespace(DOM::BoundaryPoint boundary, bool fix_collapsed_spa
if (element != start_node_code_point) { if (element != start_node_code_point) {
// 1. Call insertData(start offset, element) on start node. // 1. Call insertData(start offset, element) on start node.
auto& start_node_character_data = static_cast<DOM::CharacterData&>(*start_node); auto& start_node_character_data = static_cast<DOM::CharacterData&>(*start_node);
MUST(start_node_character_data.insert_data(start_offset, String::from_code_point(element))); MUST(start_node_character_data.insert_data(start_offset, Utf16String::from_code_point(element)));
// 2. Call deleteData(start offset + 1, 1) on start node. // 2. Call deleteData(start offset + 1, 1) on start node.
MUST(start_node_character_data.delete_data(start_offset + 1, 1)); MUST(start_node_character_data.delete_data(start_offset + 1, 1));
@ -2615,8 +2615,7 @@ bool is_whitespace_node(GC::Ref<DOM::Node> node)
auto is_tab_lf_cr_or_space = [](u32 codepoint) { auto is_tab_lf_cr_or_space = [](u32 codepoint) {
return codepoint == '\t' || codepoint == '\n' || codepoint == '\r' || codepoint == ' '; return codepoint == '\t' || codepoint == '\n' || codepoint == '\r' || codepoint == ' ';
}; };
auto code_points = character_data.data().code_points(); if (all_of(character_data.data(), is_tab_lf_cr_or_space) && (white_space_collapse == CSS::Keyword::Collapse))
if (all_of(code_points, is_tab_lf_cr_or_space) && (white_space_collapse == CSS::Keyword::Collapse))
return true; return true;
// or a Text node whose data consists only of one or more tabs (0x0009), carriage returns // or a Text node whose data consists only of one or more tabs (0x0009), carriage returns
@ -2626,7 +2625,7 @@ bool is_whitespace_node(GC::Ref<DOM::Node> node)
auto is_tab_cr_or_space = [](u32 codepoint) { auto is_tab_cr_or_space = [](u32 codepoint) {
return codepoint == '\t' || codepoint == '\r' || codepoint == ' '; return codepoint == '\t' || codepoint == '\r' || codepoint == ' ';
}; };
if (all_of(code_points, is_tab_cr_or_space) && white_space_collapse == CSS::Keyword::PreserveBreaks) if (all_of(character_data.data(), is_tab_cr_or_space) && white_space_collapse == CSS::Keyword::PreserveBreaks)
return true; return true;
return false; return false;

View file

@ -806,7 +806,7 @@ void FormAssociatedTextControlElement::handle_insert(String const& data)
String data_for_insertion = data; String data_for_insertion = data;
// FIXME: Cut by UTF-16 code units instead of raw bytes // FIXME: Cut by UTF-16 code units instead of raw bytes
if (auto max_length = text_node->max_length(); max_length.has_value()) { if (auto max_length = text_node->max_length(); max_length.has_value()) {
auto remaining_length = *max_length - text_node->data().code_points().length(); auto remaining_length = *max_length - text_node->data().length_in_code_points();
if (remaining_length < data.code_points().length()) { if (remaining_length < data.code_points().length()) {
data_for_insertion = MUST(data.substring_from_byte_offset(0, remaining_length)); data_for_insertion = MUST(data.substring_from_byte_offset(0, remaining_length));
} }
@ -832,7 +832,7 @@ void FormAssociatedTextControlElement::handle_delete(DeleteDirection direction)
MUST(set_range_text(String {}, selection_start - 1, selection_end, Bindings::SelectionMode::End)); MUST(set_range_text(String {}, selection_start - 1, selection_end, Bindings::SelectionMode::End));
} }
} else { } else {
if (selection_start < text_node->data().code_points().length()) { if (selection_start < text_node->data().length_in_code_points()) {
MUST(set_range_text(String {}, selection_start, selection_end + 1, Bindings::SelectionMode::End)); MUST(set_range_text(String {}, selection_start, selection_end + 1, Bindings::SelectionMode::End));
} }
} }
@ -982,7 +982,7 @@ void FormAssociatedTextControlElement::increment_cursor_position_to_next_word(Co
while (true) { while (true) {
if (auto offset = text_node->word_segmenter().next_boundary(m_selection_end); offset.has_value()) { if (auto offset = text_node->word_segmenter().next_boundary(m_selection_end); offset.has_value()) {
auto word = text_node->data().code_points().substring_view(m_selection_end, *offset - m_selection_end); auto word = text_node->data().substring_view(m_selection_end, *offset - m_selection_end);
if (collapse == CollapseSelection::Yes) { if (collapse == CollapseSelection::Yes) {
collapse_selection_to_offset(*offset); collapse_selection_to_offset(*offset);
} else { } else {
@ -1005,7 +1005,7 @@ void FormAssociatedTextControlElement::decrement_cursor_position_to_previous_wor
while (true) { while (true) {
if (auto offset = text_node->word_segmenter().previous_boundary(m_selection_end); offset.has_value()) { if (auto offset = text_node->word_segmenter().previous_boundary(m_selection_end); offset.has_value()) {
auto word = text_node->data().code_points().substring_view(*offset, m_selection_end - *offset); auto word = text_node->data().substring_view(*offset, m_selection_end - *offset);
if (collapse == CollapseSelection::Yes) { if (collapse == CollapseSelection::Yes) {
collapse_selection_to_offset(*offset); collapse_selection_to_offset(*offset);
} else { } else {

View file

@ -240,7 +240,7 @@ WebIDL::ExceptionOr<void> HTMLElement::set_outer_text(String const& value)
// 5. If fragment has no children, then append a new Text node whose data is the empty string and node document is this's node document to fragment. // 5. If fragment has no children, then append a new Text node whose data is the empty string and node document is this's node document to fragment.
if (!fragment->has_children()) if (!fragment->has_children())
MUST(fragment->append_child(document().create_text_node(String {}))); MUST(fragment->append_child(document().create_text_node({})));
// 6. Replace this with fragment within this's parent. // 6. Replace this with fragment within this's parent.
MUST(parent()->replace_child(fragment, *this)); MUST(parent()->replace_child(fragment, *this));
@ -276,7 +276,7 @@ GC::Ref<DOM::DocumentFragment> HTMLElement::rendered_text_fragment(StringView in
// 2. If text is not the empty string, then append a new Text node whose data is text and node document is document to fragment. // 2. If text is not the empty string, then append a new Text node whose data is text and node document is document to fragment.
if (!text.is_empty()) { if (!text.is_empty()) {
MUST(fragment->append_child(document().create_text_node(MUST(String::from_utf8(text))))); MUST(fragment->append_child(document().create_text_node(Utf16String::from_utf8(text))));
} }
// 3. While position is not past the end of input, and the code point at position is either U+000A LF or U+000D CR: // 3. While position is not past the end of input, and the code point at position is either U+000A LF or U+000D CR:

View file

@ -530,7 +530,7 @@ void HTMLInputElement::did_edit_text_node()
{ {
// An input element's dirty value flag must be set to true whenever the user interacts with the control in a way that changes the value. // An input element's dirty value flag must be set to true whenever the user interacts with the control in a way that changes the value.
auto old_value = move(m_value); auto old_value = move(m_value);
m_value = value_sanitization_algorithm(m_text_node->data()); m_value = value_sanitization_algorithm(m_text_node->data().to_utf8_but_should_be_ported_to_utf16());
m_dirty_value = true; m_dirty_value = true;
m_has_uncommitted_changes = true; m_has_uncommitted_changes = true;
@ -700,7 +700,7 @@ WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
relevant_value_was_changed(); relevant_value_was_changed();
if (m_text_node) { if (m_text_node) {
m_text_node->set_data(m_value); m_text_node->set_data(Utf16String::from_utf8(m_value));
update_placeholder_visibility(); update_placeholder_visibility();
set_the_selection_range(m_text_node->length(), m_text_node->length()); set_the_selection_range(m_text_node->length(), m_text_node->length());
@ -819,7 +819,7 @@ void HTMLInputElement::update_button_input_shadow_tree()
} }
} }
m_text_node->set_data(label.value()); m_text_node->set_data(Utf16String::from_utf8(label.value()));
update_placeholder_visibility(); update_placeholder_visibility();
} }
} }
@ -827,7 +827,7 @@ void HTMLInputElement::update_button_input_shadow_tree()
void HTMLInputElement::update_text_input_shadow_tree() void HTMLInputElement::update_text_input_shadow_tree()
{ {
if (m_text_node) { if (m_text_node) {
m_text_node->set_data(m_value); m_text_node->set_data(Utf16String::from_utf8(m_value));
update_placeholder_visibility(); update_placeholder_visibility();
} }
} }
@ -1018,7 +1018,7 @@ void HTMLInputElement::create_button_input_shadow_tree()
label = value(); label = value();
} }
} }
m_text_node = realm().create<DOM::Text>(document(), label.value()); m_text_node = realm().create<DOM::Text>(document(), Utf16String::from_utf8(label.value()));
MUST(text_container->append_child(*m_text_node)); MUST(text_container->append_child(*m_text_node));
MUST(shadow_root->append_child(*text_container)); MUST(shadow_root->append_child(*text_container));
} }
@ -1053,8 +1053,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
MUST(element->append_child(*m_placeholder_element)); MUST(element->append_child(*m_placeholder_element));
m_placeholder_text_node = realm().create<DOM::Text>(document(), String {}); m_placeholder_text_node = realm().create<DOM::Text>(document(), Utf16String::from_utf8(placeholder()));
m_placeholder_text_node->set_data(placeholder());
MUST(m_placeholder_element->append_child(*m_placeholder_text_node)); MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
// https://www.w3.org/TR/css-ui-4/#input-rules // https://www.w3.org/TR/css-ui-4/#input-rules
@ -1075,7 +1074,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
} }
MUST(element->append_child(*m_inner_text_element)); MUST(element->append_child(*m_inner_text_element));
m_text_node = realm().create<DOM::Text>(document(), move(initial_value)); m_text_node = realm().create<DOM::Text>(document(), Utf16String::from_utf8(initial_value));
handle_readonly_attribute(attribute(HTML::AttributeNames::readonly)); handle_readonly_attribute(attribute(HTML::AttributeNames::readonly));
if (type_state() == TypeAttributeState::Password) if (type_state() == TypeAttributeState::Password)
m_text_node->set_is_password_input({}, true); m_text_node->set_is_password_input({}, true);
@ -1421,7 +1420,7 @@ void HTMLInputElement::form_associated_element_attribute_changed(FlyString const
} }
} else if (name == HTML::AttributeNames::placeholder) { } else if (name == HTML::AttributeNames::placeholder) {
if (m_placeholder_text_node) { if (m_placeholder_text_node) {
m_placeholder_text_node->set_data(placeholder()); m_placeholder_text_node->set_data(Utf16String::from_utf8(placeholder()));
update_placeholder_visibility(); update_placeholder_visibility();
} }
} else if (name == HTML::AttributeNames::readonly) { } else if (name == HTML::AttributeNames::readonly) {
@ -1771,7 +1770,7 @@ void HTMLInputElement::reset_algorithm()
relevant_value_was_changed(); relevant_value_was_changed();
if (m_text_node) { if (m_text_node) {
m_text_node->set_data(m_value); m_text_node->set_data(Utf16String::from_utf8(m_value));
update_placeholder_visibility(); update_placeholder_visibility();
} }
@ -1807,7 +1806,7 @@ void HTMLInputElement::clear_algorithm()
relevant_value_was_changed(); relevant_value_was_changed();
if (m_text_node) { if (m_text_node) {
m_text_node->set_data(m_value); m_text_node->set_data(Utf16String::from_utf8(m_value));
update_placeholder_visibility(); update_placeholder_visibility();
} }

View file

@ -191,7 +191,7 @@ void HTMLTextAreaElement::set_value(String const& value)
// the text control, unselecting any selected text and resetting the selection direction to "none". // the text control, unselecting any selected text and resetting the selection direction to "none".
if (api_value() != old_api_value) { if (api_value() != old_api_value) {
if (m_text_node) { if (m_text_node) {
m_text_node->set_data(m_raw_value); m_text_node->set_data(Utf16String::from_utf8(m_raw_value));
update_placeholder_visibility(); update_placeholder_visibility();
set_the_selection_range(m_text_node->length(), m_text_node->length()); set_the_selection_range(m_text_node->length(), m_text_node->length());
@ -369,14 +369,13 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed()
m_placeholder_element->set_use_pseudo_element(CSS::PseudoElement::Placeholder); m_placeholder_element->set_use_pseudo_element(CSS::PseudoElement::Placeholder);
MUST(element->append_child(*m_placeholder_element)); MUST(element->append_child(*m_placeholder_element));
m_placeholder_text_node = realm().create<DOM::Text>(document(), String {}); m_placeholder_text_node = realm().create<DOM::Text>(document(), Utf16String::from_utf8(get_attribute_value(HTML::AttributeNames::placeholder)));
m_placeholder_text_node->set_data(get_attribute_value(HTML::AttributeNames::placeholder));
MUST(m_placeholder_element->append_child(*m_placeholder_text_node)); MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
m_inner_text_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); m_inner_text_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
MUST(element->append_child(*m_inner_text_element)); MUST(element->append_child(*m_inner_text_element));
m_text_node = realm().create<DOM::Text>(document(), String {}); m_text_node = realm().create<DOM::Text>(document(), Utf16String {});
handle_readonly_attribute(attribute(HTML::AttributeNames::readonly)); handle_readonly_attribute(attribute(HTML::AttributeNames::readonly));
// NOTE: If `children_changed()` was called before now, `m_raw_value` will hold the text content. // NOTE: If `children_changed()` was called before now, `m_raw_value` will hold the text content.
// Otherwise, it will get filled in whenever that does get called. // Otherwise, it will get filled in whenever that does get called.
@ -442,7 +441,7 @@ void HTMLTextAreaElement::form_associated_element_attribute_changed(FlyString co
{ {
if (name == HTML::AttributeNames::placeholder) { if (name == HTML::AttributeNames::placeholder) {
if (m_placeholder_text_node) if (m_placeholder_text_node)
m_placeholder_text_node->set_data(value.value_or(String {})); m_placeholder_text_node->set_data(Utf16String::from_utf8(value.value_or(String {})));
} else if (name == HTML::AttributeNames::readonly) { } else if (name == HTML::AttributeNames::readonly) {
handle_readonly_attribute(value); handle_readonly_attribute(value);
} else if (name == HTML::AttributeNames::maxlength) { } else if (name == HTML::AttributeNames::maxlength) {
@ -453,7 +452,7 @@ void HTMLTextAreaElement::form_associated_element_attribute_changed(FlyString co
void HTMLTextAreaElement::did_edit_text_node() void HTMLTextAreaElement::did_edit_text_node()
{ {
VERIFY(m_text_node); VERIFY(m_text_node);
set_raw_value(m_text_node->data()); set_raw_value(m_text_node->data().to_utf8_but_should_be_ported_to_utf16());
// Any time the user causes the element's raw value to change, the user agent must queue an element task on the user // Any time the user causes the element's raw value to change, the user agent must queue an element task on the user
// interaction task source given the textarea element to fire an event named input at the textarea element, with the // interaction task source given the textarea element to fire an event named input at the textarea element, with the

View file

@ -2483,11 +2483,11 @@ static String visible_text_in_range(DOM::Range const& range)
if (range.start_container() == range.end_container() && is<DOM::Text>(*range.start_container())) { if (range.start_container() == range.end_container() && is<DOM::Text>(*range.start_container())) {
if (!range.start_container()->layout_node()) if (!range.start_container()->layout_node())
return String {}; return String {};
return MUST(static_cast<DOM::Text const&>(*range.start_container()).data().substring_from_byte_offset(range.start_offset(), range.end_offset() - range.start_offset())); return static_cast<DOM::Text const&>(*range.start_container()).data().substring_view(range.start_offset(), range.end_offset() - range.start_offset()).to_utf8_but_should_be_ported_to_utf16();
} }
if (is<DOM::Text>(*range.start_container()) && range.start_container()->layout_node()) if (is<DOM::Text>(*range.start_container()) && range.start_container()->layout_node())
builder.append(static_cast<DOM::Text const&>(*range.start_container()).data().bytes_as_string_view().substring_view(range.start_offset())); builder.append(static_cast<DOM::Text const&>(*range.start_container()).data().substring_view(range.start_offset()));
range.for_each_contained([&](GC::Ref<DOM::Node> node) { range.for_each_contained([&](GC::Ref<DOM::Node> node) {
if (is<DOM::Text>(*node) && node->layout_node()) if (is<DOM::Text>(*node) && node->layout_node())
@ -2496,7 +2496,7 @@ static String visible_text_in_range(DOM::Range const& range)
}); });
if (is<DOM::Text>(*range.end_container()) && range.end_container()->layout_node()) if (is<DOM::Text>(*range.end_container()) && range.end_container()->layout_node())
builder.append(static_cast<DOM::Text const&>(*range.end_container()).data().bytes_as_string_view().substring_view(0, range.end_offset())); builder.append(static_cast<DOM::Text const&>(*range.end_container()).data().substring_view(0, range.end_offset()));
return MUST(builder.to_string()); return MUST(builder.to_string());
} }

View file

@ -576,7 +576,7 @@ void HTMLParser::handle_initial(HTMLToken& token)
// -> A comment token // -> A comment token
if (token.is_comment()) { if (token.is_comment()) {
// Insert a comment as the last child of the Document object. // Insert a comment as the last child of the Document object.
auto comment = realm().create<DOM::Comment>(document(), token.comment()); auto comment = realm().create<DOM::Comment>(document(), Utf16String::from_utf8(token.comment()));
MUST(document().append_child(*comment)); MUST(document().append_child(*comment));
return; return;
} }
@ -632,7 +632,7 @@ void HTMLParser::handle_before_html(HTMLToken& token)
// -> A comment token // -> A comment token
if (token.is_comment()) { if (token.is_comment()) {
// Insert a comment as the last child of the Document object. // Insert a comment as the last child of the Document object.
auto comment = realm().create<DOM::Comment>(document(), token.comment()); auto comment = realm().create<DOM::Comment>(document(), Utf16String::from_utf8(token.comment()));
MUST(document().append_child(*comment)); MUST(document().append_child(*comment));
return; return;
} }
@ -985,7 +985,7 @@ void HTMLParser::handle_before_head(HTMLToken& token)
void HTMLParser::insert_comment(HTMLToken& token) void HTMLParser::insert_comment(HTMLToken& token)
{ {
auto adjusted_insertion_location = find_appropriate_place_for_inserting_node(); auto adjusted_insertion_location = find_appropriate_place_for_inserting_node();
adjusted_insertion_location.parent->insert_before(realm().create<DOM::Comment>(document(), token.comment()), adjusted_insertion_location.insert_before_sibling); adjusted_insertion_location.parent->insert_before(realm().create<DOM::Comment>(document(), Utf16String::from_utf8(token.comment())), adjusted_insertion_location.insert_before_sibling);
} }
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead // https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead
@ -1392,7 +1392,7 @@ DOM::Text* HTMLParser::find_character_insertion_node()
if (adjusted_insertion_location.insert_before_sibling) { if (adjusted_insertion_location.insert_before_sibling) {
if (is_text_node(adjusted_insertion_location.insert_before_sibling->previous_sibling())) if (is_text_node(adjusted_insertion_location.insert_before_sibling->previous_sibling()))
return static_cast<DOM::Text*>(adjusted_insertion_location.insert_before_sibling->previous_sibling()); return static_cast<DOM::Text*>(adjusted_insertion_location.insert_before_sibling->previous_sibling());
auto new_text_node = realm().create<DOM::Text>(document(), String {}); auto new_text_node = realm().create<DOM::Text>(document(), Utf16String {});
adjusted_insertion_location.parent->insert_before(*new_text_node, *adjusted_insertion_location.insert_before_sibling); adjusted_insertion_location.parent->insert_before(*new_text_node, *adjusted_insertion_location.insert_before_sibling);
return new_text_node; return new_text_node;
} }
@ -1400,7 +1400,7 @@ DOM::Text* HTMLParser::find_character_insertion_node()
return nullptr; return nullptr;
if (is_text_node(adjusted_insertion_location.parent->last_child())) if (is_text_node(adjusted_insertion_location.parent->last_child()))
return static_cast<DOM::Text*>(adjusted_insertion_location.parent->last_child()); return static_cast<DOM::Text*>(adjusted_insertion_location.parent->last_child());
auto new_text_node = realm().create<DOM::Text>(document(), String {}); auto new_text_node = realm().create<DOM::Text>(document(), Utf16String {});
MUST(adjusted_insertion_location.parent->append_child(*new_text_node)); MUST(adjusted_insertion_location.parent->append_child(*new_text_node));
return new_text_node; return new_text_node;
} }
@ -1410,9 +1410,9 @@ void HTMLParser::flush_character_insertions()
if (m_character_insertion_builder.is_empty()) if (m_character_insertion_builder.is_empty())
return; return;
if (m_character_insertion_node->data().is_empty()) if (m_character_insertion_node->data().is_empty())
m_character_insertion_node->set_data(MUST(m_character_insertion_builder.to_string())); m_character_insertion_node->set_data(m_character_insertion_builder.to_utf16_string());
else else
(void)m_character_insertion_node->append_data(MUST(m_character_insertion_builder.to_string())); (void)m_character_insertion_node->append_data(m_character_insertion_builder.to_utf16_string());
m_character_insertion_builder.clear(); m_character_insertion_builder.clear();
} }
@ -1579,7 +1579,7 @@ void HTMLParser::handle_after_body(HTMLToken& token)
if (token.is_comment()) { if (token.is_comment()) {
// Insert a comment as the last child of the first element in the stack of open elements (the html element). // Insert a comment as the last child of the first element in the stack of open elements (the html element).
auto& insertion_location = m_stack_of_open_elements.first(); auto& insertion_location = m_stack_of_open_elements.first();
MUST(insertion_location.append_child(realm().create<DOM::Comment>(document(), token.comment()))); MUST(insertion_location.append_child(realm().create<DOM::Comment>(document(), Utf16String::from_utf8(token.comment()))));
return; return;
} }
@ -1631,7 +1631,7 @@ void HTMLParser::handle_after_after_body(HTMLToken& token)
// -> A comment token // -> A comment token
if (token.is_comment()) { if (token.is_comment()) {
// Insert a comment as the last child of the Document object. // Insert a comment as the last child of the Document object.
auto comment = realm().create<DOM::Comment>(document(), token.comment()); auto comment = realm().create<DOM::Comment>(document(), Utf16String::from_utf8(token.comment()));
MUST(document().append_child(*comment)); MUST(document().append_child(*comment));
return; return;
} }
@ -4626,7 +4626,7 @@ void HTMLParser::handle_after_after_frameset(HTMLToken& token)
// -> A comment token // -> A comment token
if (token.is_comment()) { if (token.is_comment()) {
// Insert a comment as the last child of the Document object. // Insert a comment as the last child of the Document object.
auto comment = document().realm().create<DOM::Comment>(document(), token.comment()); auto comment = document().realm().create<DOM::Comment>(document(), Utf16String::from_utf8(token.comment()));
MUST(document().append_child(comment)); MUST(document().append_child(comment));
return; return;
} }
@ -5123,11 +5123,12 @@ enum class AttributeMode {
Yes, Yes,
}; };
static String escape_string(StringView string, AttributeMode attribute_mode) template<OneOf<Utf8View, Utf16View> ViewType>
static String escape_string(ViewType const& string, AttributeMode attribute_mode)
{ {
// https://html.spec.whatwg.org/multipage/parsing.html#escapingString // https://html.spec.whatwg.org/multipage/parsing.html#escapingString
StringBuilder builder; StringBuilder builder;
for (auto code_point : Utf8View { string }) { for (auto code_point : string) {
// 1. Replace any occurrence of the "&" character by the string "&amp;". // 1. Replace any occurrence of the "&" character by the string "&amp;".
if (code_point == '&') if (code_point == '&')
builder.append("&amp;"sv); builder.append("&amp;"sv);
@ -5179,7 +5180,7 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, SerializableSh
// followed by a U+0022 QUOTATION MARK character ("). // followed by a U+0022 QUOTATION MARK character (").
if (element.is_value().has_value() && !element.has_attribute(AttributeNames::is)) { if (element.is_value().has_value() && !element.has_attribute(AttributeNames::is)) {
builder.append(" is=\""sv); builder.append(" is=\""sv);
builder.append(escape_string(element.is_value().value(), AttributeMode::Yes)); builder.append(escape_string(element.is_value().value().code_points(), AttributeMode::Yes));
builder.append('"'); builder.append('"');
} }
@ -5212,7 +5213,7 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, SerializableSh
builder.append(attribute.name()); builder.append(attribute.name());
builder.append("=\""sv); builder.append("=\""sv);
builder.append(escape_string(attribute.value(), AttributeMode::Yes)); builder.append(escape_string(attribute.value().code_points(), AttributeMode::Yes));
builder.append('"'); builder.append('"');
}); });
@ -5335,7 +5336,7 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, SerializableSh
} }
// Otherwise, append the value of current node's data IDL attribute, escaped as described below. // Otherwise, append the value of current node's data IDL attribute, escaped as described below.
builder.append(escape_string(text_node.data(), AttributeMode::No)); builder.append(escape_string(text_node.data().utf16_view(), AttributeMode::No));
} }
if (is<DOM::Comment>(current_node)) { if (is<DOM::Comment>(current_node)) {

View file

@ -228,7 +228,7 @@ private:
Vector<HTMLToken> m_pending_table_character_tokens; Vector<HTMLToken> m_pending_table_character_tokens;
GC::Ptr<DOM::Text> m_character_insertion_node; GC::Ptr<DOM::Text> m_character_insertion_node;
StringBuilder m_character_insertion_builder; StringBuilder m_character_insertion_builder { StringBuilder::Mode::UTF16 };
} SWIFT_UNSAFE_REFERENCE; } SWIFT_UNSAFE_REFERENCE;
RefPtr<CSS::CSSStyleValue const> parse_dimension_value(StringView); RefPtr<CSS::CSSStyleValue const> parse_dimension_value(StringView);

View file

@ -754,7 +754,7 @@ static WebIDL::ExceptionOr<String> serialize_comment(DOM::Comment const& comment
if (comment.data().contains("--"sv)) if (comment.data().contains("--"sv))
return WebIDL::InvalidStateError::create(comment.realm(), "Comment data contains two adjacent hyphens"_string); return WebIDL::InvalidStateError::create(comment.realm(), "Comment data contains two adjacent hyphens"_string);
if (comment.data().ends_with('-')) if (comment.data().ends_with("-"sv))
return WebIDL::InvalidStateError::create(comment.realm(), "Comment data ends with a hyphen"_string); return WebIDL::InvalidStateError::create(comment.realm(), "Comment data ends with a hyphen"_string);
} }
@ -776,7 +776,7 @@ static WebIDL::ExceptionOr<String> serialize_text(DOM::Text const& text, Require
// 1. If the require well-formed flag is set (its value is true), and node's data contains characters that are not matched by the XML Char production, // 1. If the require well-formed flag is set (its value is true), and node's data contains characters that are not matched by the XML Char production,
// then throw an exception; the serialization of this node's data would not be well-formed. // then throw an exception; the serialization of this node's data would not be well-formed.
if (require_well_formed == RequireWellFormed::Yes) { if (require_well_formed == RequireWellFormed::Yes) {
for (u32 code_point : text.data().code_points()) { for (u32 code_point : text.data()) {
if (!is_valid_xml_char(code_point)) if (!is_valid_xml_char(code_point))
return WebIDL::InvalidStateError::create(text.realm(), "Text contains characters not allowed in XML"_string); return WebIDL::InvalidStateError::create(text.realm(), "Text contains characters not allowed in XML"_string);
} }
@ -786,16 +786,16 @@ static WebIDL::ExceptionOr<String> serialize_text(DOM::Text const& text, Require
auto markup = text.data(); auto markup = text.data();
// 3. Replace any occurrences of "&" in markup by "&amp;". // 3. Replace any occurrences of "&" in markup by "&amp;".
markup = MUST(markup.replace("&"sv, "&amp;"sv, ReplaceMode::All)); markup = markup.replace("&"sv, "&amp;"sv, ReplaceMode::All);
// 4. Replace any occurrences of "<" in markup by "&lt;". // 4. Replace any occurrences of "<" in markup by "&lt;".
markup = MUST(markup.replace("<"sv, "&lt;"sv, ReplaceMode::All)); markup = markup.replace("<"sv, "&lt;"sv, ReplaceMode::All);
// 5. Replace any occurrences of ">" in markup by "&gt;". // 5. Replace any occurrences of ">" in markup by "&gt;".
markup = MUST(markup.replace(">"sv, "&gt;"sv, ReplaceMode::All)); markup = markup.replace(">"sv, "&gt;"sv, ReplaceMode::All);
// 6. Return the value of markup. // 6. Return the value of markup.
return markup; return markup.to_utf8_but_should_be_ported_to_utf16();
} }
// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-documentfragment-node // https://w3c.github.io/DOM-Parsing/#xml-serializing-a-documentfragment-node

View file

@ -1756,7 +1756,7 @@ bool FormattingContext::can_skip_is_anonymous_text_run(Box& box)
if (box.is_anonymous() && !box.is_generated() && !box.first_child_of_type<BlockContainer>()) { if (box.is_anonymous() && !box.is_generated() && !box.first_child_of_type<BlockContainer>()) {
bool contains_only_white_space = true; bool contains_only_white_space = true;
box.for_each_in_subtree([&](auto const& node) { box.for_each_in_subtree([&](auto const& node) {
if (!is<TextNode>(node) || !static_cast<TextNode const&>(node).dom_node().data().bytes_as_string_view().is_whitespace()) { if (!is<TextNode>(node) || !static_cast<TextNode const&>(node).dom_node().data().is_ascii_whitespace()) {
contains_only_white_space = false; contains_only_white_space = false;
return TraversalDecision::Break; return TraversalDecision::Break;
} }

View file

@ -322,7 +322,7 @@ String const& TextNode::text_for_rendering() const
void TextNode::compute_text_for_rendering() void TextNode::compute_text_for_rendering()
{ {
if (dom_node().is_password_input()) { if (dom_node().is_password_input()) {
m_text_for_rendering = MUST(String::repeated('*', dom_node().data().code_points().length())); m_text_for_rendering = MUST(String::repeated('*', dom_node().data().length_in_code_points()));
return; return;
} }
@ -332,7 +332,7 @@ void TextNode::compute_text_for_rendering()
auto const maybe_lang = parent_element ? parent_element->lang() : Optional<String> {}; auto const maybe_lang = parent_element ? parent_element->lang() : Optional<String> {};
auto const lang = maybe_lang.has_value() ? maybe_lang.value() : Optional<StringView> {}; auto const lang = maybe_lang.has_value() ? maybe_lang.value() : Optional<StringView> {};
auto data = apply_text_transform(dom_node().data(), computed_values().text_transform(), lang).release_value_but_fixme_should_propagate_errors(); auto data = apply_text_transform(dom_node().data().to_utf8_but_should_be_ported_to_utf16(), computed_values().text_transform(), lang).release_value_but_fixme_should_propagate_errors();
auto data_view = data.bytes_as_string_view(); auto data_view = data.bytes_as_string_view();

View file

@ -242,7 +242,7 @@ void TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Ps
// FIXME: Handle images, and multiple values // FIXME: Handle images, and multiple values
if (new_content.type == CSS::ContentData::Type::String) { if (new_content.type == CSS::ContentData::Type::String) {
auto text = document.realm().create<DOM::Text>(document, new_content.data); auto text = document.realm().create<DOM::Text>(document, Utf16String::from_utf8(new_content.data));
auto text_node = document.heap().allocate<TextNode>(document, *text); auto text_node = document.heap().allocate<TextNode>(document, *text);
text_node->set_generated_for(pseudo_element, element); text_node->set_generated_for(pseudo_element, element);

View file

@ -599,12 +599,12 @@ void Selection::move_offset_to_next_word(bool collapse_selection)
auto& text_node = static_cast<DOM::Text&>(*anchor_node); auto& text_node = static_cast<DOM::Text&>(*anchor_node);
while (true) { while (true) {
auto focus_offset = this->focus_offset(); auto focus_offset = this->focus_offset();
if (focus_offset == text_node.data().bytes_as_string_view().length()) { if (focus_offset == text_node.data().length_in_code_units()) {
return; return;
} }
if (auto offset = text_node.word_segmenter().next_boundary(focus_offset); offset.has_value()) { if (auto offset = text_node.word_segmenter().next_boundary(focus_offset); offset.has_value()) {
auto word = text_node.data().code_points().substring_view(focus_offset, *offset - focus_offset); auto word = text_node.data().substring_view(focus_offset, *offset - focus_offset);
if (collapse_selection) { if (collapse_selection) {
MUST(collapse(anchor_node, *offset)); MUST(collapse(anchor_node, *offset));
m_document->reset_cursor_blink_cycle(); m_document->reset_cursor_blink_cycle();
@ -629,7 +629,7 @@ void Selection::move_offset_to_previous_word(bool collapse_selection)
while (true) { while (true) {
auto focus_offset = this->focus_offset(); auto focus_offset = this->focus_offset();
if (auto offset = text_node.word_segmenter().previous_boundary(focus_offset); offset.has_value()) { if (auto offset = text_node.word_segmenter().previous_boundary(focus_offset); offset.has_value()) {
auto word = text_node.data().code_points().unicode_substring_view(*offset, focus_offset - *offset); auto word = text_node.data().substring_view(*offset, focus_offset - *offset);
if (collapse_selection) { if (collapse_selection) {
MUST(collapse(anchor_node, *offset)); MUST(collapse(anchor_node, *offset));
m_document->reset_cursor_blink_cycle(); m_document->reset_cursor_blink_cycle();

View file

@ -234,6 +234,11 @@ JS::ThrowCompletionOr<String> to_string(JS::VM& vm, JS::Value value)
return value.to_string(vm); return value.to_string(vm);
} }
JS::ThrowCompletionOr<Utf16String> to_utf16_string(JS::VM& vm, JS::Value value)
{
return value.to_utf16_string(vm);
}
JS::ThrowCompletionOr<String> to_usv_string(JS::VM& vm, JS::Value value) JS::ThrowCompletionOr<String> to_usv_string(JS::VM& vm, JS::Value value)
{ {
return value.to_well_formed_string(vm); return value.to_well_formed_string(vm);

View file

@ -23,6 +23,7 @@ ErrorOr<ByteBuffer> get_buffer_source_copy(JS::Object const& buffer_source);
JS::Completion call_user_object_operation(CallbackType& callback, String const& operation_name, Optional<JS::Value> this_argument, ReadonlySpan<JS::Value> args); JS::Completion call_user_object_operation(CallbackType& callback, String const& operation_name, Optional<JS::Value> this_argument, ReadonlySpan<JS::Value> args);
JS::ThrowCompletionOr<String> to_string(JS::VM&, JS::Value); JS::ThrowCompletionOr<String> to_string(JS::VM&, JS::Value);
JS::ThrowCompletionOr<Utf16String> to_utf16_string(JS::VM&, JS::Value);
JS::ThrowCompletionOr<String> to_usv_string(JS::VM&, JS::Value); JS::ThrowCompletionOr<String> to_usv_string(JS::VM&, JS::Value);
JS::ThrowCompletionOr<String> to_byte_string(JS::VM&, JS::Value); JS::ThrowCompletionOr<String> to_byte_string(JS::VM&, JS::Value);

View file

@ -245,27 +245,25 @@ void XMLDocumentBuilder::text(StringView data)
{ {
if (m_has_error) if (m_has_error)
return; return;
auto last = m_current_node->last_child();
if (last && last->is_text()) { if (auto* last = m_current_node->last_child(); last && last->is_text()) {
auto& text_node = static_cast<DOM::Text&>(*last); auto& text_node = static_cast<DOM::Text&>(*last);
text_builder.append(text_node.data()); m_text_builder.append(text_node.data());
text_builder.append(data); m_text_builder.append(data);
text_node.set_data(MUST(text_builder.to_string())); text_node.set_data(m_text_builder.to_utf16_string());
text_builder.clear(); m_text_builder.clear();
} else { } else if (!data.is_empty()) {
if (!data.is_empty()) { auto node = m_document->create_text_node(Utf16String::from_utf8(data));
auto node = m_document->create_text_node(MUST(String::from_utf8(data)));
MUST(m_current_node->append_child(node)); MUST(m_current_node->append_child(node));
} }
} }
}
void XMLDocumentBuilder::comment(StringView data) void XMLDocumentBuilder::comment(StringView data)
{ {
if (m_has_error || !m_current_node) if (m_has_error || !m_current_node)
return; return;
MUST(m_current_node->append_child(m_document->create_comment(MUST(String::from_utf8(data))))); MUST(m_current_node->append_child(m_document->create_comment(Utf16String::from_utf8(data))));
} }
void XMLDocumentBuilder::cdata_section(StringView data) void XMLDocumentBuilder::cdata_section(StringView data)
@ -273,7 +271,7 @@ void XMLDocumentBuilder::cdata_section(StringView data)
if (m_has_error || !m_current_node) if (m_has_error || !m_current_node)
return; return;
auto section = MUST(m_document->create_cdata_section(MUST(String::from_utf8(data)))); auto section = MUST(m_document->create_cdata_section(Utf16String::from_utf8(data)));
MUST(m_current_node->append_child(section)); MUST(m_current_node->append_child(section));
} }
@ -282,7 +280,7 @@ void XMLDocumentBuilder::processing_instruction(StringView target, StringView da
if (m_has_error || !m_current_node) if (m_has_error || !m_current_node)
return; return;
auto processing_instruction = MUST(m_document->create_processing_instruction(MUST(String::from_utf8(target)), MUST(String::from_utf8(data)))); auto processing_instruction = MUST(m_document->create_processing_instruction(MUST(String::from_utf8(target)), Utf16String::from_utf8(data)));
MUST(m_current_node->append_child(processing_instruction)); MUST(m_current_node->append_child(processing_instruction));
} }

View file

@ -47,7 +47,7 @@ private:
GC::Ptr<DOM::Node> m_current_node; GC::Ptr<DOM::Node> m_current_node;
XMLScriptingSupport m_scripting_support { XMLScriptingSupport::Enabled }; XMLScriptingSupport m_scripting_support { XMLScriptingSupport::Enabled };
bool m_has_error { false }; bool m_has_error { false };
StringBuilder text_builder; StringBuilder m_text_builder { StringBuilder::Mode::UTF16 };
struct NamespaceAndPrefix { struct NamespaceAndPrefix {
FlyString ns; FlyString ns;

View file

@ -650,7 +650,7 @@ void ConnectionFromClient::get_dom_node_inner_html(u64 page_id, Web::UniqueNodeI
html = element.inner_html().release_value_but_fixme_should_propagate_errors(); html = element.inner_html().release_value_but_fixme_should_propagate_errors();
} else if (dom_node->is_text() || dom_node->is_comment()) { } else if (dom_node->is_text() || dom_node->is_comment()) {
auto const& character_data = static_cast<Web::DOM::CharacterData const&>(*dom_node); auto const& character_data = static_cast<Web::DOM::CharacterData const&>(*dom_node);
html = character_data.data(); html = character_data.data().to_utf8_but_should_be_ported_to_utf16();
} else { } else {
return; return;
} }
@ -671,7 +671,7 @@ void ConnectionFromClient::get_dom_node_outer_html(u64 page_id, Web::UniqueNodeI
html = element.outer_html().release_value_but_fixme_should_propagate_errors(); html = element.outer_html().release_value_but_fixme_should_propagate_errors();
} else if (dom_node->is_text() || dom_node->is_comment()) { } else if (dom_node->is_text() || dom_node->is_comment()) {
auto const& character_data = static_cast<Web::DOM::CharacterData const&>(*dom_node); auto const& character_data = static_cast<Web::DOM::CharacterData const&>(*dom_node);
html = character_data.data(); html = character_data.data().to_utf8_but_should_be_ported_to_utf16();
} else { } else {
return; return;
} }
@ -692,7 +692,7 @@ void ConnectionFromClient::set_dom_node_outer_html(u64 page_id, Web::UniqueNodeI
element.set_outer_html(html).release_value_but_fixme_should_propagate_errors(); element.set_outer_html(html).release_value_but_fixme_should_propagate_errors();
} else if (dom_node->is_text() || dom_node->is_comment()) { } else if (dom_node->is_text() || dom_node->is_comment()) {
auto& character_data = static_cast<Web::DOM::CharacterData&>(*dom_node); auto& character_data = static_cast<Web::DOM::CharacterData&>(*dom_node);
character_data.set_data(html); character_data.set_data(Utf16String::from_utf8(html));
} else { } else {
async_did_finish_editing_dom_node(page_id, {}); async_did_finish_editing_dom_node(page_id, {});
return; return;
@ -710,7 +710,7 @@ void ConnectionFromClient::set_dom_node_text(u64 page_id, Web::UniqueNodeID node
} }
auto& character_data = static_cast<Web::DOM::CharacterData&>(*dom_node); auto& character_data = static_cast<Web::DOM::CharacterData&>(*dom_node);
character_data.set_data(text); character_data.set_data(Utf16String::from_utf8(text));
async_did_finish_editing_dom_node(page_id, character_data.unique_id()); async_did_finish_editing_dom_node(page_id, character_data.unique_id());
} }
@ -804,7 +804,7 @@ void ConnectionFromClient::create_child_text_node(u64 page_id, Web::UniqueNodeID
return; return;
} }
auto text_node = dom_node->realm().create<Web::DOM::Text>(dom_node->document(), "text"_string); auto text_node = dom_node->realm().create<Web::DOM::Text>(dom_node->document(), "text"_utf16);
dom_node->append_child(text_node).release_value_but_fixme_should_propagate_errors(); dom_node->append_child(text_node).release_value_but_fixme_should_propagate_errors();
async_did_finish_editing_dom_node(page_id, text_node->unique_id()); async_did_finish_editing_dom_node(page_id, text_node->unique_id());

View file

@ -667,7 +667,7 @@ void PageClient::page_did_mutate_dom(FlyString const& type, Web::DOM::Node const
mutation = WebView::AttributeMutation { *attribute_name, element.attribute(*attribute_name) }; mutation = WebView::AttributeMutation { *attribute_name, element.attribute(*attribute_name) };
} else if (type == Web::DOM::MutationType::characterData) { } else if (type == Web::DOM::MutationType::characterData) {
auto const& character_data = as<Web::DOM::CharacterData>(target); auto const& character_data = as<Web::DOM::CharacterData>(target);
mutation = WebView::CharacterDataMutation { character_data.data() }; mutation = WebView::CharacterDataMutation { character_data.data().to_utf8_but_should_be_ported_to_utf16() };
} else if (type == Web::DOM::MutationType::childList) { } else if (type == Web::DOM::MutationType::childList) {
Vector<Web::UniqueNodeID> added; Vector<Web::UniqueNodeID> added;
added.ensure_capacity(added_nodes.length()); added.ensure_capacity(added_nodes.length());