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);
}
bool Segmenter::should_continue_beyond_word(Utf8View const& word)
bool Segmenter::should_continue_beyond_word(Utf16View const& word)
{
for (auto code_point : word) {
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);
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; }

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);
// 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()) {
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));
}

View file

@ -51,12 +51,11 @@ void AccessibilityTreeNode::serialize_tree_as_json(JsonObjectSerializer<StringBu
} else {
VERIFY_NOT_REACHED();
}
} else if (value()->is_text()) {
MUST(object.add("type"sv, "text"sv));
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()) {

View file

@ -12,8 +12,8 @@ namespace Web::DOM {
GC_DEFINE_ALLOCATOR(CDATASection);
CDATASection::CDATASection(Document& document, String const& data)
: Text(document, NodeType::CDATA_SECTION_NODE, data)
CDATASection::CDATASection(Document& document, Utf16String 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; }
private:
CDATASection(Document&, String const&);
CDATASection(Document&, Utf16String);
virtual void initialize(JS::Realm&) override;
};

View file

@ -17,9 +17,9 @@ namespace Web::DOM {
GC_DEFINE_ALLOCATOR(CharacterData);
CharacterData::CharacterData(Document& document, NodeType type, String const& data)
CharacterData::CharacterData(Document& document, NodeType type, Utf16String data)
: 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
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.
// 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
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.
// FIXME: This is very inefficient!
auto utf16_string = Utf16String::from_utf8(m_data);
auto length = utf16_string.length_in_code_units();
auto length = m_data.length_in_code_units();
// 2. If offset is greater than length, then throw an "IndexSizeError" DOMException.
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
// to the end of nodes data, and then return.
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.
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
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.
// FIXME: This is very inefficient!
auto utf16_string = Utf16String::from_utf8(m_data);
auto length = utf16_string.length_in_code_units();
auto length = m_data.length_in_code_units();
// 2. If offset is greater than length, then throw an "IndexSizeError" DOMException.
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.
// 6. Let delete offset be offset + datas length.
// 7. Starting from delete offset code units, remove count code units from nodes data.
auto before_data = utf16_string.substring_view(0, offset);
auto inserted_data = Utf16String::from_utf8(data);
auto after_data = utf16_string.substring_view(offset + count);
auto before_data = m_data.substring_view(0, offset);
auto after_data = m_data.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(inserted_data);
full_data.append(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;
// OPTIMIZATION: Skip UTF-8 encoding if the characters are the same.
if (!characters_are_the_same) {
m_data = MUST(full_view.to_utf8());
}
m_data = full_data.to_utf16_string();
// 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.
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
// 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.
for (auto* range : Range::live_ranges()) {
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
// offset by datas length and decrease it by count.
for (auto* range : Range::live_ranges()) {
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.
@ -136,7 +125,7 @@ WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t coun
parent()->children_changed(nullptr);
// 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 {};
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
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.
return replace_data(this->length_in_utf16_code_units(), 0, data);
}
// 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.
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)
{
// 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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ template<typename NodeType>
class ChildNode {
public:
// 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);
@ -46,7 +46,7 @@ public:
}
// 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);
@ -70,7 +70,7 @@ public:
}
// 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);
@ -117,7 +117,7 @@ protected:
ChildNode() = default;
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);
@ -142,7 +142,7 @@ private:
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);

View file

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

View file

@ -13,16 +13,16 @@ namespace Web::DOM {
GC_DEFINE_ALLOCATOR(Comment);
Comment::Comment(Document& document, String const& data)
: CharacterData(document, NodeType::COMMENT_NODE, data)
Comment::Comment(Document& document, Utf16String data)
: CharacterData(document, NodeType::COMMENT_NODE, move(data))
{
}
// 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());
return realm.create<Comment>(window.associated_document(), data);
return realm.create<Comment>(window.associated_document(), move(data));
}
void Comment::initialize(JS::Realm& realm)

View file

@ -15,13 +15,13 @@ class Comment final : public CharacterData {
GC_DECLARE_ALLOCATOR(Comment);
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 FlyString node_name() const override { return "#comment"_fly_string; }
private:
Comment(Document&, String const&);
Comment(Document&, Utf16String);
virtual void initialize(JS::Realm&) override;
};

View file

@ -3,5 +3,5 @@
// https://dom.spec.whatwg.org/#comment
[Exposed=Window]
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
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.
auto html_document = HTML::HTMLDocument::create(realm());

View file

@ -22,7 +22,7 @@ public:
virtual ~DOMImplementation();
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);
// https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature

View file

@ -6,7 +6,7 @@
interface DOMImplementation {
[NewObject] DocumentType createDocumentType(DOMString qualifiedName, DOMString publicId, DOMString systemId);
[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
};

View file

@ -2122,13 +2122,13 @@ GC::Ref<DocumentFragment> Document::create_document_fragment()
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
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.
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);
// 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
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.
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);
// 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()

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_ns(Optional<FlyString> const& namespace_, String const& qualified_name, Variant<String, ElementCreationOptions> const& options);
GC::Ref<DocumentFragment> create_document_fragment();
GC::Ref<Text> create_text_node(String const& data);
WebIDL::ExceptionOr<GC::Ref<CDATASection>> create_cdata_section(String const& data);
GC::Ref<Comment> create_comment(String const& data);
WebIDL::ExceptionOr<GC::Ref<ProcessingInstruction>> create_processing_instruction(String const& target, String const& data);
GC::Ref<Text> create_text_node(Utf16String data);
WebIDL::ExceptionOr<GC::Ref<CDATASection>> create_cdata_section(Utf16String data);
GC::Ref<Comment> create_comment(Utf16String 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_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 createElementNS([FlyString] DOMString? namespace, DOMString qualifiedName, optional (DOMString or ElementCreationOptions) options = {});
DocumentFragment createDocumentFragment();
Text createTextNode(DOMString data);
[NewObject] CDATASection createCDATASection(DOMString data);
Comment createComment(DOMString data);
[NewObject] ProcessingInstruction createProcessingInstruction(DOMString target, DOMString data);
Text createTextNode(Utf16DOMString data);
[NewObject] CDATASection createCDATASection(Utf16DOMString data);
Comment createComment(Utf16DOMString data);
[NewObject] ProcessingInstruction createProcessingInstruction(DOMString target, Utf16DOMString data);
[NewObject] Attr createAttribute(DOMString localName);
[NewObject] Attr createAttributeNS([FlyString] DOMString? namespace, DOMString qualifiedName);

View file

@ -26,12 +26,12 @@
namespace Web {
// 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 body_element = MUST(DOM::create_element(document, HTML::TagNames::body, Namespace::HTML));
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();
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());
// Well-formed XML documents contain only properly encoded characters
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;
}
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)) {
// FIXME: Insert error message into the document.
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.
document->completely_finish_loading();
@ -172,7 +172,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
if (source.is_error()) {
// FIXME: Insert error message into the document.
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.
document->completely_finish_loading();
@ -184,7 +184,7 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_xml_document(HTML::Navig
if (result.is_error()) {
// FIXME: Insert error message into the document.
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.
}
@ -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
// 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));
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));
});
@ -283,11 +283,13 @@ static WebIDL::ExceptionOr<GC::Ref<DOM::Document>> load_media_document(HTML::Nav
// 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
// 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));
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));
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));
TRY(img_element->set_attribute(HTML::AttributeNames::src, url_string));
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) {
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::controls, String {}));
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) {
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::controls, String {}));
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 {
// 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
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.
auto text = realm().create<DOM::Text>(document(), data);

View file

@ -306,7 +306,7 @@ public:
bool is_actually_disabled() const;
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
ErrorOr<void> scroll_into_view(Optional<Variant<bool, ScrollIntoViewOptions>> = {});

View file

@ -77,7 +77,7 @@ interface Element : Node {
HTMLCollection getElementsByClassName(DOMString className);
[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
readonly attribute Element? nextElementSibling;

View file

@ -186,7 +186,7 @@ Optional<String> Node::text_content() const
// If CharacterData, return thiss data.
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 (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.
else if (is<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.
// 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.
StringBuilder data;
StringBuilder data(StringBuilder::Mode::UTF16);
for (auto const& text_node : contiguous_exclusive_text_nodes_excluding_self(node))
data.append(text_node->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.
auto* current_node = node.next_sibling();
@ -363,7 +362,7 @@ Optional<String> Node::node_value() const
// If CharacterData, return thiss data.
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.
@ -382,7 +381,7 @@ void Node::set_node_value(Optional<String> const& maybe_value)
as<Attr>(this)->set_value(move(value));
} else if (is<CharacterData>(this)) {
// 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.
@ -1870,7 +1869,7 @@ bool Node::is_uninteresting_whitespace_node() const
{
if (!is<Text>(*this))
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;
if (!layout_node())
return true;
@ -1936,10 +1935,10 @@ void Node::serialize_tree_as_json(JsonObjectSerializer<StringBuilder>& object) c
MUST(object.add("type"sv, "text"));
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()) {
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()) {
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));
@ -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.
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.
replace_all(node);

View file

@ -5,7 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <AK/Utf16String.h>
#include <AK/Vector.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentFragment.h>
@ -15,7 +15,7 @@
namespace Web::DOM {
// 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.
// 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.
// 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>>())
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)

View file

@ -13,6 +13,6 @@
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
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.
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 {};
}
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.
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 {};
}
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.
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_ns(Optional<FlyString>, FlyString const&);
WebIDL::ExceptionOr<void> prepend(Vector<Variant<GC::Root<Node>, String>> const& nodes);
WebIDL::ExceptionOr<void> append(Vector<Variant<GC::Root<Node>, String>> const& nodes);
WebIDL::ExceptionOr<void> replace_children(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>, Utf16String>> 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);
GC::Ref<HTMLCollection> get_elements_by_class_name(StringView);

View file

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

View file

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

View file

@ -22,7 +22,7 @@ public:
String const& target() const { return m_target; }
private:
ProcessingInstruction(Document&, String const& data, String const& target);
ProcessingInstruction(Document&, Utf16String data, String const& target);
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.
if (start_container() == end_container() && is<Text>(*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.
@ -621,7 +621,7 @@ WebIDL::ExceptionOr<GC::Ref<DocumentFragment>> Range::extract()
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.
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.
return fragment;
@ -711,7 +711,7 @@ WebIDL::ExceptionOr<GC::Ref<DocumentFragment>> Range::extract()
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.
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:
else if (first_partially_contained_child) {
@ -749,7 +749,7 @@ WebIDL::ExceptionOr<GC::Ref<DocumentFragment>> Range::extract()
TRY(fragment->append_child(clone));
// 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:
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,
// 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)) {
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 {};
}
@ -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.
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.
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.
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).
TRY(set_start(*new_node, new_offset));

View file

@ -18,13 +18,13 @@ namespace Web::DOM {
GC_DEFINE_ALLOCATOR(Text);
Text::Text(Document& document, String const& data)
: CharacterData(document, NodeType::TEXT_NODE, data)
Text::Text(Document& document, Utf16String data)
: CharacterData(document, NodeType::TEXT_NODE, move(data))
{
}
Text::Text(Document& document, NodeType type, String const& data)
: CharacterData(document, type, data)
Text::Text(Document& document, NodeType type, Utf16String 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
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.
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
@ -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.
TRY(replace_data(offset, count, String {}));
TRY(replace_data(offset, count, {}));
// 9. Return new node.
return new_node;
}
// https://dom.spec.whatwg.org/#dom-text-wholetext
String Text::whole_text()
Utf16String Text::whole_text()
{
// 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
@ -141,11 +141,11 @@ String Text::whole_text()
current_node = current_node->next_sibling();
}
StringBuilder builder;
StringBuilder builder(StringBuilder::Mode::UTF16);
for (auto const& text_node : nodes)
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
@ -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.
// 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;
for (auto code_point : Utf8View(data())) {
for (auto code_point : data()) {
auto bidi_class = Unicode::bidirectional_class(code_point);
if (first_is_one_of(bidi_class, Unicode::BidiClass::LeftToRight, Unicode::BidiClass::RightToLeftArabic, Unicode::BidiClass::RightToLeft)) {
found_character_bidi_class = bidi_class;

View file

@ -22,7 +22,7 @@ class Text
public:
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
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); }
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; }
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;
protected:
Text(Document&, String const&);
Text(Document&, NodeType, String const&);
Text(Document&, Utf16String);
Text(Document&, NodeType, Utf16String);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;

View file

@ -5,10 +5,10 @@
// https://dom.spec.whatwg.org/#text
[Exposed=Window]
interface Text : CharacterData {
constructor(optional DOMString data = "");
constructor(optional Utf16DOMString data = "");
[NewObject] Text splitText(unsigned long offset);
readonly attribute DOMString wholeText;
readonly attribute Utf16DOMString wholeText;
};
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) {
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)) {
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));
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;
}
}
@ -1796,7 +1796,7 @@ bool command_insert_text_action(DOM::Document& document, Utf16View const& value)
// 13. If node is a Text node:
if (is<DOM::Text>(*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.
MUST(selection.collapse(node, offset));
@ -1812,7 +1812,7 @@ bool command_insert_text_action(DOM::Document& document, Utf16View const& value)
node->first_child()->remove();
// 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.
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) {
// 1. Call insertData(start offset, element) on 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.
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) {
return codepoint == '\t' || codepoint == '\n' || codepoint == '\r' || codepoint == ' ';
};
auto code_points = character_data.data().code_points();
if (all_of(code_points, is_tab_lf_cr_or_space) && (white_space_collapse == CSS::Keyword::Collapse))
if (all_of(character_data.data(), is_tab_lf_cr_or_space) && (white_space_collapse == CSS::Keyword::Collapse))
return true;
// 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) {
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 false;

View file

@ -806,7 +806,7 @@ void FormAssociatedTextControlElement::handle_insert(String const& data)
String data_for_insertion = data;
// FIXME: Cut by UTF-16 code units instead of raw bytes
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()) {
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));
}
} 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));
}
}
@ -982,7 +982,7 @@ void FormAssociatedTextControlElement::increment_cursor_position_to_next_word(Co
while (true) {
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) {
collapse_selection_to_offset(*offset);
} else {
@ -1005,7 +1005,7 @@ void FormAssociatedTextControlElement::decrement_cursor_position_to_previous_wor
while (true) {
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) {
collapse_selection_to_offset(*offset);
} 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.
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.
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.
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:

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.
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_has_uncommitted_changes = true;
@ -700,7 +700,7 @@ WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
relevant_value_was_changed();
if (m_text_node) {
m_text_node->set_data(m_value);
m_text_node->set_data(Utf16String::from_utf8(m_value));
update_placeholder_visibility();
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();
}
}
@ -827,7 +827,7 @@ void HTMLInputElement::update_button_input_shadow_tree()
void HTMLInputElement::update_text_input_shadow_tree()
{
if (m_text_node) {
m_text_node->set_data(m_value);
m_text_node->set_data(Utf16String::from_utf8(m_value));
update_placeholder_visibility();
}
}
@ -1018,7 +1018,7 @@ void HTMLInputElement::create_button_input_shadow_tree()
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(shadow_root->append_child(*text_container));
}
@ -1053,8 +1053,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
MUST(element->append_child(*m_placeholder_element));
m_placeholder_text_node = realm().create<DOM::Text>(document(), String {});
m_placeholder_text_node->set_data(placeholder());
m_placeholder_text_node = realm().create<DOM::Text>(document(), Utf16String::from_utf8(placeholder()));
MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
// 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));
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));
if (type_state() == TypeAttributeState::Password)
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) {
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();
}
} else if (name == HTML::AttributeNames::readonly) {
@ -1771,7 +1770,7 @@ void HTMLInputElement::reset_algorithm()
relevant_value_was_changed();
if (m_text_node) {
m_text_node->set_data(m_value);
m_text_node->set_data(Utf16String::from_utf8(m_value));
update_placeholder_visibility();
}
@ -1807,7 +1806,7 @@ void HTMLInputElement::clear_algorithm()
relevant_value_was_changed();
if (m_text_node) {
m_text_node->set_data(m_value);
m_text_node->set_data(Utf16String::from_utf8(m_value));
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".
if (api_value() != old_api_value) {
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();
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);
MUST(element->append_child(*m_placeholder_element));
m_placeholder_text_node = realm().create<DOM::Text>(document(), String {});
m_placeholder_text_node->set_data(get_attribute_value(HTML::AttributeNames::placeholder));
m_placeholder_text_node = realm().create<DOM::Text>(document(), Utf16String::from_utf8(get_attribute_value(HTML::AttributeNames::placeholder)));
MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
m_inner_text_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
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));
// 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.
@ -442,7 +441,7 @@ void HTMLTextAreaElement::form_associated_element_attribute_changed(FlyString co
{
if (name == HTML::AttributeNames::placeholder) {
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) {
handle_readonly_attribute(value);
} 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()
{
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
// 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()->layout_node())
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())
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) {
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())
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());
}

View file

@ -576,7 +576,7 @@ void HTMLParser::handle_initial(HTMLToken& token)
// -> A comment token
if (token.is_comment()) {
// 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));
return;
}
@ -632,7 +632,7 @@ void HTMLParser::handle_before_html(HTMLToken& token)
// -> A comment token
if (token.is_comment()) {
// 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));
return;
}
@ -985,7 +985,7 @@ void HTMLParser::handle_before_head(HTMLToken& token)
void HTMLParser::insert_comment(HTMLToken& token)
{
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
@ -1392,7 +1392,7 @@ DOM::Text* HTMLParser::find_character_insertion_node()
if (adjusted_insertion_location.insert_before_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());
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);
return new_text_node;
}
@ -1400,7 +1400,7 @@ DOM::Text* HTMLParser::find_character_insertion_node()
return nullptr;
if (is_text_node(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));
return new_text_node;
}
@ -1410,9 +1410,9 @@ void HTMLParser::flush_character_insertions()
if (m_character_insertion_builder.is_empty())
return;
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
(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();
}
@ -1579,7 +1579,7 @@ void HTMLParser::handle_after_body(HTMLToken& token)
if (token.is_comment()) {
// 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();
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;
}
@ -1631,7 +1631,7 @@ void HTMLParser::handle_after_after_body(HTMLToken& token)
// -> A comment token
if (token.is_comment()) {
// 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));
return;
}
@ -4626,7 +4626,7 @@ void HTMLParser::handle_after_after_frameset(HTMLToken& token)
// -> A comment token
if (token.is_comment()) {
// 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));
return;
}
@ -5123,11 +5123,12 @@ enum class AttributeMode {
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
StringBuilder builder;
for (auto code_point : Utf8View { string }) {
for (auto code_point : string) {
// 1. Replace any occurrence of the "&" character by the string "&amp;".
if (code_point == '&')
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 (").
if (element.is_value().has_value() && !element.has_attribute(AttributeNames::is)) {
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('"');
}
@ -5212,7 +5213,7 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, SerializableSh
builder.append(attribute.name());
builder.append("=\""sv);
builder.append(escape_string(attribute.value(), AttributeMode::Yes));
builder.append(escape_string(attribute.value().code_points(), AttributeMode::Yes));
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.
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)) {

View file

@ -228,7 +228,7 @@ private:
Vector<HTMLToken> m_pending_table_character_tokens;
GC::Ptr<DOM::Text> m_character_insertion_node;
StringBuilder m_character_insertion_builder;
StringBuilder m_character_insertion_builder { StringBuilder::Mode::UTF16 };
} SWIFT_UNSAFE_REFERENCE;
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))
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);
}
@ -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,
// then throw an exception; the serialization of this node's data would not be well-formed.
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))
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();
// 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;".
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;".
markup = MUST(markup.replace(">"sv, "&gt;"sv, ReplaceMode::All));
markup = markup.replace(">"sv, "&gt;"sv, ReplaceMode::All);
// 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

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>()) {
bool contains_only_white_space = true;
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;
return TraversalDecision::Break;
}

View file

@ -322,7 +322,7 @@ String const& TextNode::text_for_rendering() const
void TextNode::compute_text_for_rendering()
{
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;
}
@ -332,7 +332,7 @@ void TextNode::compute_text_for_rendering()
auto const maybe_lang = parent_element ? parent_element->lang() : Optional<String> {};
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();

View file

@ -242,7 +242,7 @@ void TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Ps
// FIXME: Handle images, and multiple values
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);
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);
while (true) {
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;
}
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) {
MUST(collapse(anchor_node, *offset));
m_document->reset_cursor_blink_cycle();
@ -629,7 +629,7 @@ void Selection::move_offset_to_previous_word(bool collapse_selection)
while (true) {
auto focus_offset = this->focus_offset();
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) {
MUST(collapse(anchor_node, *offset));
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);
}
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)
{
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::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_byte_string(JS::VM&, JS::Value);

View file

@ -245,18 +245,16 @@ void XMLDocumentBuilder::text(StringView data)
{
if (m_has_error)
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);
text_builder.append(text_node.data());
text_builder.append(data);
text_node.set_data(MUST(text_builder.to_string()));
text_builder.clear();
} else {
if (!data.is_empty()) {
auto node = m_document->create_text_node(MUST(String::from_utf8(data)));
MUST(m_current_node->append_child(node));
}
m_text_builder.append(text_node.data());
m_text_builder.append(data);
text_node.set_data(m_text_builder.to_utf16_string());
m_text_builder.clear();
} else if (!data.is_empty()) {
auto node = m_document->create_text_node(Utf16String::from_utf8(data));
MUST(m_current_node->append_child(node));
}
}
@ -265,7 +263,7 @@ void XMLDocumentBuilder::comment(StringView data)
if (m_has_error || !m_current_node)
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)
@ -273,7 +271,7 @@ void XMLDocumentBuilder::cdata_section(StringView data)
if (m_has_error || !m_current_node)
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));
}
@ -282,7 +280,7 @@ void XMLDocumentBuilder::processing_instruction(StringView target, StringView da
if (m_has_error || !m_current_node)
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));
}

View file

@ -47,7 +47,7 @@ private:
GC::Ptr<DOM::Node> m_current_node;
XMLScriptingSupport m_scripting_support { XMLScriptingSupport::Enabled };
bool m_has_error { false };
StringBuilder text_builder;
StringBuilder m_text_builder { StringBuilder::Mode::UTF16 };
struct NamespaceAndPrefix {
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();
} else if (dom_node->is_text() || dom_node->is_comment()) {
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 {
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();
} else if (dom_node->is_text() || dom_node->is_comment()) {
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 {
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();
} else if (dom_node->is_text() || dom_node->is_comment()) {
auto& character_data = static_cast<Web::DOM::CharacterData&>(*dom_node);
character_data.set_data(html);
character_data.set_data(Utf16String::from_utf8(html));
} else {
async_did_finish_editing_dom_node(page_id, {});
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);
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());
}
@ -804,7 +804,7 @@ void ConnectionFromClient::create_child_text_node(u64 page_id, Web::UniqueNodeID
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();
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) };
} else if (type == Web::DOM::MutationType::characterData) {
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) {
Vector<Web::UniqueNodeID> added;
added.ensure_capacity(added_nodes.length());