Make NetworkSendQueue use CString instead of String for UTF-8 data
https://bugs.webkit.org/show_bug.cgi?id=213714

Reviewed by Darin Adler.

Source/WebCore:

Use CString instead of String in NetworkSendQueue as all data is to be encoded as UTF-8.
This allows converting and allocating the UTF-8 string only once.
Covered by existing tests.

* Modules/mediastream/RTCDataChannel.cpp:
(WebCore::RTCDataChannel::createMessageQueue):
(WebCore::RTCDataChannel::send):
* Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.cpp:
(WebCore::LibWebRTCDataChannelHandler::sendStringData):
(WebCore::LibWebRTCDataChannelHandler::sendRawData):
* Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.h:
* fileapi/NetworkSendQueue.cpp:
(WebCore::NetworkSendQueue::enqueue):
(WebCore::NetworkSendQueue::processMessages):
* fileapi/NetworkSendQueue.h:
* platform/mediastream/RTCDataChannelHandler.h:
* platform/mock/RTCDataChannelHandlerMock.cpp:
(WebCore::RTCDataChannelHandlerMock::sendStringData):
* platform/mock/RTCDataChannelHandlerMock.h:

Source/WebKit:

Update code to use CString/DataReference to transmit WebSocket text messages.

* NetworkProcess/NetworkSocketChannel.cpp:
(WebKit::NetworkSocketChannel::sendString):
* NetworkProcess/NetworkSocketChannel.h:
* NetworkProcess/NetworkSocketChannel.messages.in:
* NetworkProcess/WebSocketTask.h:
(WebKit::WebSocketTask::sendString):
* NetworkProcess/cocoa/WebSocketTaskCocoa.h:
* NetworkProcess/cocoa/WebSocketTaskCocoa.mm:
(WebKit::WebSocketTask::sendString):
* NetworkProcess/soup/WebSocketTaskSoup.h:
* NetworkProcess/soup/WebSocketTaskSoup.cpp:
(WebKit::WebSocketTask::sendString):
* WebProcess/Network/WebSocketChannel.cpp:
(WebKit::WebSocketChannel::createMessageQueue):
(WebKit::WebSocketChannel::send):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@263797 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 0baa142..2f7cd0d 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,30 @@
+2020-07-01  Youenn Fablet  <youenn@apple.com>
+
+        Make NetworkSendQueue use CString instead of String for UTF-8 data
+        https://bugs.webkit.org/show_bug.cgi?id=213714
+
+        Reviewed by Darin Adler.
+
+        Use CString instead of String in NetworkSendQueue as all data is to be encoded as UTF-8.
+        This allows converting and allocating the UTF-8 string only once.
+        Covered by existing tests.
+
+        * Modules/mediastream/RTCDataChannel.cpp:
+        (WebCore::RTCDataChannel::createMessageQueue):
+        (WebCore::RTCDataChannel::send):
+        * Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.cpp:
+        (WebCore::LibWebRTCDataChannelHandler::sendStringData):
+        (WebCore::LibWebRTCDataChannelHandler::sendRawData):
+        * Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.h:
+        * fileapi/NetworkSendQueue.cpp:
+        (WebCore::NetworkSendQueue::enqueue):
+        (WebCore::NetworkSendQueue::processMessages):
+        * fileapi/NetworkSendQueue.h:
+        * platform/mediastream/RTCDataChannelHandler.h:
+        * platform/mock/RTCDataChannelHandlerMock.cpp:
+        (WebCore::RTCDataChannelHandlerMock::sendStringData):
+        * platform/mock/RTCDataChannelHandlerMock.h:
+
 2020-07-01  Alexey Shvayka  <shvaikalesh@gmail.com>
 
         Use more efficient makeString() instead of StringBuilder
diff --git a/Source/WebCore/Modules/mediastream/RTCDataChannel.cpp b/Source/WebCore/Modules/mediastream/RTCDataChannel.cpp
index 38f39dd..001d4e2 100644
--- a/Source/WebCore/Modules/mediastream/RTCDataChannel.cpp
+++ b/Source/WebCore/Modules/mediastream/RTCDataChannel.cpp
@@ -66,8 +66,8 @@
 
 NetworkSendQueue RTCDataChannel::createMessageQueue(Document& document, RTCDataChannel& channel)
 {
-    return { document, [&channel](const String& data) {
-        if (!channel.m_handler->sendStringData(data))
+    return { document, [&channel](auto& utf8) {
+        if (!channel.m_handler->sendStringData(utf8))
             channel.scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Error sending string through RTCDataChannel."_s);
     }, [&channel](auto* data, size_t length) {
         if (!channel.m_handler->sendRawData(data, length))
@@ -121,8 +121,10 @@
     if (m_readyState != RTCDataChannelState::Open)
         return Exception { InvalidStateError };
 
-    m_bufferedAmount += data.utf8().length();
-    m_messageQueue.enqueue(data);
+    // FIXME: We might want to use strict conversion like WebSocket.
+    auto utf8 = data.utf8();
+    m_bufferedAmount += utf8.length();
+    m_messageQueue.enqueue(WTFMove(utf8));
     return { };
 }
 
diff --git a/Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.cpp b/Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.cpp
index fcd690e..479699c 100644
--- a/Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.cpp
+++ b/Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.cpp
@@ -89,15 +89,14 @@
     checkState();
 }
 
-bool LibWebRTCDataChannelHandler::sendStringData(const String& text)
+bool LibWebRTCDataChannelHandler::sendStringData(const CString& utf8Text)
 {
-    auto utf8Text = text.utf8();
     return m_channel->Send({ rtc::CopyOnWriteBuffer(utf8Text.data(), utf8Text.length()), false });
 }
 
 bool LibWebRTCDataChannelHandler::sendRawData(const char* data, size_t length)
 {
-    return m_channel->Send({rtc::CopyOnWriteBuffer(data, length), true});
+    return m_channel->Send({ rtc::CopyOnWriteBuffer(data, length), true });
 }
 
 void LibWebRTCDataChannelHandler::close()
diff --git a/Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.h b/Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.h
index 5d2cd89..aea2694 100644
--- a/Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.h
+++ b/Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCDataChannelHandler.h
@@ -60,7 +60,7 @@
     // RTCDataChannelHandler API
     void setClient(RTCDataChannelHandlerClient&) final;
     void checkState();
-    bool sendStringData(const String&) final;
+    bool sendStringData(const CString&) final;
     bool sendRawData(const char*, size_t) final;
     void close() final;
 
diff --git a/Source/WebCore/fileapi/NetworkSendQueue.cpp b/Source/WebCore/fileapi/NetworkSendQueue.cpp
index 985e530..75cf143 100644
--- a/Source/WebCore/fileapi/NetworkSendQueue.cpp
+++ b/Source/WebCore/fileapi/NetworkSendQueue.cpp
@@ -40,13 +40,13 @@
 
 NetworkSendQueue::~NetworkSendQueue() = default;
 
-void NetworkSendQueue::enqueue(const String& data)
+void NetworkSendQueue::enqueue(CString&& utf8)
 {
     if (m_queue.isEmpty()) {
-        m_writeString(data);
+        m_writeString(utf8);
         return;
     }
-    m_queue.append(data);
+    m_queue.append(WTFMove(utf8));
 }
 
 void NetworkSendQueue::enqueue(const JSC::ArrayBuffer& binaryData, unsigned byteOffset, unsigned byteLength)
@@ -80,8 +80,8 @@
 {
     while (!m_queue.isEmpty()) {
         bool shouldStopProcessing = false;
-        switchOn(m_queue.first(), [this](const String& message) {
-            m_writeString(message);
+        switchOn(m_queue.first(), [this](const CString& utf8) {
+            m_writeString(utf8);
         }, [this](Ref<SharedBuffer>& data) {
             m_writeRawData(data->data(), data->size());
         }, [this, &shouldStopProcessing](UniqueRef<BlobLoader>& loader) {
diff --git a/Source/WebCore/fileapi/NetworkSendQueue.h b/Source/WebCore/fileapi/NetworkSendQueue.h
index e5c256f..27ffa1f 100644
--- a/Source/WebCore/fileapi/NetworkSendQueue.h
+++ b/Source/WebCore/fileapi/NetworkSendQueue.h
@@ -31,6 +31,7 @@
 #include <wtf/UniqueRef.h>
 #include <wtf/Variant.h>
 #include <wtf/WeakPtr.h>
+#include <wtf/text/CString.h>
 
 namespace JSC {
 class ArrayBuffer;
@@ -45,14 +46,14 @@
 
 class WEBCORE_EXPORT NetworkSendQueue {
 public:
-    using WriteString = Function<void(const String&)>;
+    using WriteString = Function<void(const CString& utf8)>;
     using WriteRawData = Function<void(const char*, size_t)>;
     enum class Continue { No, Yes };
     using ProcessError = Function<Continue(ExceptionCode)>;
     NetworkSendQueue(Document&, WriteString&&, WriteRawData&&, ProcessError&&);
     ~NetworkSendQueue();
 
-    void enqueue(const String&);
+    void enqueue(CString&& utf8);
     void enqueue(const JSC::ArrayBuffer&, unsigned byteOffset, unsigned byteLength);
     void enqueue(Blob&);
 
@@ -61,7 +62,7 @@
 private:
     void processMessages();
 
-    using Message = Variant<String, Ref<SharedBuffer>, UniqueRef<BlobLoader>>;
+    using Message = Variant<CString, Ref<SharedBuffer>, UniqueRef<BlobLoader>>;
     Deque<Message> m_queue;
 
     WTF::WeakPtr<Document> m_document;
diff --git a/Source/WebCore/platform/mediastream/RTCDataChannelHandler.h b/Source/WebCore/platform/mediastream/RTCDataChannelHandler.h
index 778c745..ca6cf72 100644
--- a/Source/WebCore/platform/mediastream/RTCDataChannelHandler.h
+++ b/Source/WebCore/platform/mediastream/RTCDataChannelHandler.h
@@ -49,7 +49,7 @@
 
     virtual void setClient(RTCDataChannelHandlerClient&) = 0;
 
-    virtual bool sendStringData(const String&) = 0;
+    virtual bool sendStringData(const CString&) = 0;
     virtual bool sendRawData(const char*, size_t) = 0;
     virtual void close() = 0;
 };
diff --git a/Source/WebCore/platform/mock/RTCDataChannelHandlerMock.cpp b/Source/WebCore/platform/mock/RTCDataChannelHandlerMock.cpp
index f6f8111..e806a61 100644
--- a/Source/WebCore/platform/mock/RTCDataChannelHandlerMock.cpp
+++ b/Source/WebCore/platform/mock/RTCDataChannelHandlerMock.cpp
@@ -48,9 +48,9 @@
     m_timerEvents.append(adoptRef(new TimerEvent(this, WTFMove(notifier))));
 }
 
-bool RTCDataChannelHandlerMock::sendStringData(const String& string)
+bool RTCDataChannelHandlerMock::sendStringData(const CString& string)
 {
-    m_client->didReceiveStringData(string);
+    m_client->didReceiveStringData(String::fromUTF8(string));
     return true;
 }
 
diff --git a/Source/WebCore/platform/mock/RTCDataChannelHandlerMock.h b/Source/WebCore/platform/mock/RTCDataChannelHandlerMock.h
index 2b16b2a..f3cfa68 100644
--- a/Source/WebCore/platform/mock/RTCDataChannelHandlerMock.h
+++ b/Source/WebCore/platform/mock/RTCDataChannelHandlerMock.h
@@ -40,7 +40,7 @@
 private:
     void setClient(RTCDataChannelHandlerClient&) final;
 
-    bool sendStringData(const String&) final;
+    bool sendStringData(const CString&) final;
     bool sendRawData(const char*, size_t) final;
     void close() final;
 
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index f583812..d4759ed 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,28 @@
+2020-07-01  Youenn Fablet  <youenn@apple.com>
+
+        Make NetworkSendQueue use CString instead of String for UTF-8 data
+        https://bugs.webkit.org/show_bug.cgi?id=213714
+
+        Reviewed by Darin Adler.
+
+        Update code to use CString/DataReference to transmit WebSocket text messages.
+
+        * NetworkProcess/NetworkSocketChannel.cpp:
+        (WebKit::NetworkSocketChannel::sendString):
+        * NetworkProcess/NetworkSocketChannel.h:
+        * NetworkProcess/NetworkSocketChannel.messages.in:
+        * NetworkProcess/WebSocketTask.h:
+        (WebKit::WebSocketTask::sendString):
+        * NetworkProcess/cocoa/WebSocketTaskCocoa.h:
+        * NetworkProcess/cocoa/WebSocketTaskCocoa.mm:
+        (WebKit::WebSocketTask::sendString):
+        * NetworkProcess/soup/WebSocketTaskSoup.h:
+        * NetworkProcess/soup/WebSocketTaskSoup.cpp:
+        (WebKit::WebSocketTask::sendString):
+        * WebProcess/Network/WebSocketChannel.cpp:
+        (WebKit::WebSocketChannel::createMessageQueue):
+        (WebKit::WebSocketChannel::send):
+
 2020-07-01  James Savage  <james.savage@apple.com>
 
         Fix Swift overlay build after r263727.
diff --git a/Source/WebKit/NetworkProcess/NetworkSocketChannel.cpp b/Source/WebKit/NetworkProcess/NetworkSocketChannel.cpp
index 3dbdca4..265670b 100644
--- a/Source/WebKit/NetworkProcess/NetworkSocketChannel.cpp
+++ b/Source/WebKit/NetworkProcess/NetworkSocketChannel.cpp
@@ -71,7 +71,7 @@
         m_socket->cancel();
 }
 
-void NetworkSocketChannel::sendString(const String& message, CompletionHandler<void()>&& callback)
+void NetworkSocketChannel::sendString(const IPC::DataReference& message, CompletionHandler<void()>&& callback)
 {
     m_socket->sendString(message, WTFMove(callback));
 }
diff --git a/Source/WebKit/NetworkProcess/NetworkSocketChannel.h b/Source/WebKit/NetworkProcess/NetworkSocketChannel.h
index fcdb9d4..5147e3d 100644
--- a/Source/WebKit/NetworkProcess/NetworkSocketChannel.h
+++ b/Source/WebKit/NetworkProcess/NetworkSocketChannel.h
@@ -70,7 +70,7 @@
     void didSendHandshakeRequest(WebCore::ResourceRequest&&);
     void didReceiveHandshakeResponse(WebCore::ResourceResponse&&);
 
-    void sendString(const String&, CompletionHandler<void()>&&);
+    void sendString(const IPC::DataReference&, CompletionHandler<void()>&&);
     void sendData(const IPC::DataReference&, CompletionHandler<void()>&&);
     void close(int32_t code, const String& reason);
 
diff --git a/Source/WebKit/NetworkProcess/NetworkSocketChannel.messages.in b/Source/WebKit/NetworkProcess/NetworkSocketChannel.messages.in
index c8be2b4..bd02b91 100644
--- a/Source/WebKit/NetworkProcess/NetworkSocketChannel.messages.in
+++ b/Source/WebKit/NetworkProcess/NetworkSocketChannel.messages.in
@@ -21,7 +21,7 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 messages -> NetworkSocketChannel NotRefCounted {
-    SendString(String message) -> () Async
+    SendString(IPC::DataReference message) -> () Async
     SendData(IPC::DataReference data) -> () Async
     Close(int32_t code, String reason)
 }
diff --git a/Source/WebKit/NetworkProcess/WebSocketTask.h b/Source/WebKit/NetworkProcess/WebSocketTask.h
index 0479920..25f2175 100644
--- a/Source/WebKit/NetworkProcess/WebSocketTask.h
+++ b/Source/WebKit/NetworkProcess/WebSocketTask.h
@@ -38,7 +38,7 @@
 public:
     typedef uint64_t TaskIdentifier;
 
-    void sendString(const String&, CompletionHandler<void()>&&) { }
+    void sendString(const IPC::DataReference&, CompletionHandler<void()>&&) { }
     void sendData(const IPC::DataReference&, CompletionHandler<void()>&&) { }
     void close(int32_t code, const String& reason) { }
 
diff --git a/Source/WebKit/NetworkProcess/cocoa/WebSocketTaskCocoa.h b/Source/WebKit/NetworkProcess/cocoa/WebSocketTaskCocoa.h
index 6c5f468..dac6935 100644
--- a/Source/WebKit/NetworkProcess/cocoa/WebSocketTaskCocoa.h
+++ b/Source/WebKit/NetworkProcess/cocoa/WebSocketTaskCocoa.h
@@ -47,7 +47,7 @@
     WebSocketTask(NetworkSocketChannel&, RetainPtr<NSURLSessionWebSocketTask>&&);
     ~WebSocketTask();
 
-    void sendString(const String&, CompletionHandler<void()>&&);
+    void sendString(const IPC::DataReference&, CompletionHandler<void()>&&);
     void sendData(const IPC::DataReference&, CompletionHandler<void()>&&);
     void close(int32_t code, const String& reason);
 
diff --git a/Source/WebKit/NetworkProcess/cocoa/WebSocketTaskCocoa.mm b/Source/WebKit/NetworkProcess/cocoa/WebSocketTaskCocoa.mm
index d9434d3..d764735 100644
--- a/Source/WebKit/NetworkProcess/cocoa/WebSocketTaskCocoa.mm
+++ b/Source/WebKit/NetworkProcess/cocoa/WebSocketTaskCocoa.mm
@@ -115,10 +115,15 @@
     m_channel.didClose(code, reason);
 }
 
-void WebSocketTask::sendString(const String& text , CompletionHandler<void()>&& callback)
+void WebSocketTask::sendString(const IPC::DataReference& utf8String, CompletionHandler<void()>&& callback)
 {
-    auto message = adoptNS([[NSURLSessionWebSocketMessage alloc] initWithString: text]);
-    [m_task sendMessage: message.get() completionHandler: makeBlockPtr([callback = WTFMove(callback)](NSError * _Nullable) mutable {
+    auto text = adoptNS([[NSString alloc] initWithBytes:utf8String.data() length:utf8String.size() encoding:NSUTF8StringEncoding]);
+    if (!text) {
+        callback();
+        return;
+    }
+    auto message = adoptNS([[NSURLSessionWebSocketMessage alloc] initWithString:text.get()]);
+    [m_task sendMessage:message.get() completionHandler:makeBlockPtr([callback = WTFMove(callback)](NSError * _Nullable) mutable {
         // Workaround rdar://problem/55324926 until it gets fixed.
         callOnMainRunLoop(WTFMove(callback));
     }).get()];
@@ -127,8 +132,8 @@
 void WebSocketTask::sendData(const IPC::DataReference& data, CompletionHandler<void()>&& callback)
 {
     auto nsData = adoptNS([[NSData alloc] initWithBytes:data.data() length:data.size()]);
-    auto message = adoptNS([[NSURLSessionWebSocketMessage alloc] initWithData: nsData.get()]);
-    [m_task sendMessage: message.get() completionHandler: makeBlockPtr([callback = WTFMove(callback)](NSError * _Nullable) mutable {
+    auto message = adoptNS([[NSURLSessionWebSocketMessage alloc] initWithData:nsData.get()]);
+    [m_task sendMessage:message.get() completionHandler:makeBlockPtr([callback = WTFMove(callback)](NSError * _Nullable) mutable {
         // Workaround rdar://problem/55324926 until it gets fixed.
         callOnMainRunLoop(WTFMove(callback));
     }).get()];
@@ -140,7 +145,7 @@
     if (code == WebCore::WebSocketChannel::CloseEventCodeNotSpecified)
         code = 1005;
     auto nsData = adoptNS([[NSData alloc] initWithBytes:reason.utf8().data() length:reason.sizeInBytes()]);
-    [m_task cancelWithCloseCode: (NSURLSessionWebSocketCloseCode)code reason: nsData.get()];
+    [m_task cancelWithCloseCode:(NSURLSessionWebSocketCloseCode)code reason:nsData.get()];
 }
 
 WebSocketTask::TaskIdentifier WebSocketTask::identifier() const
diff --git a/Source/WebKit/NetworkProcess/soup/WebSocketTaskSoup.cpp b/Source/WebKit/NetworkProcess/soup/WebSocketTaskSoup.cpp
index 0ff1c1a..07c5b40 100644
--- a/Source/WebKit/NetworkProcess/soup/WebSocketTaskSoup.cpp
+++ b/Source/WebKit/NetworkProcess/soup/WebSocketTaskSoup.cpp
@@ -185,13 +185,12 @@
     m_channel.didClose(code, reason);
 }
 
-void WebSocketTask::sendString(const String& text, CompletionHandler<void()>&& callback)
+void WebSocketTask::sendString(const IPC::DataReference& utf8, CompletionHandler<void()>&& callback)
 {
     if (m_connection && soup_websocket_connection_get_state(m_connection.get()) == SOUP_WEBSOCKET_STATE_OPEN) {
-        CString utf8 = text.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD);
 #if SOUP_CHECK_VERSION(2, 67, 3)
         // Soup is going to copy the data immediately, so we can use g_bytes_new_static() here to avoid more data copies.
-        GRefPtr<GBytes> bytes = adoptGRef(g_bytes_new_static(utf8.data(), utf8.length()));
+        GRefPtr<GBytes> bytes = adoptGRef(g_bytes_new_static(utf8.data(), utf8.size()));
         soup_websocket_connection_send_message(m_connection.get(), SOUP_WEBSOCKET_DATA_TEXT, bytes.get());
 #else
         soup_websocket_connection_send_text(m_connection.get(), utf8.data());
diff --git a/Source/WebKit/NetworkProcess/soup/WebSocketTaskSoup.h b/Source/WebKit/NetworkProcess/soup/WebSocketTaskSoup.h
index 4f076bf..961b959 100644
--- a/Source/WebKit/NetworkProcess/soup/WebSocketTaskSoup.h
+++ b/Source/WebKit/NetworkProcess/soup/WebSocketTaskSoup.h
@@ -41,7 +41,7 @@
     WebSocketTask(NetworkSocketChannel&, SoupSession*, SoupMessage*, const String& protocol);
     ~WebSocketTask();
 
-    void sendString(const String&, CompletionHandler<void()>&&);
+    void sendString(const IPC::DataReference&, CompletionHandler<void()>&&);
     void sendData(const IPC::DataReference&, CompletionHandler<void()>&&);
     void close(int32_t code, const String& reason);
 
diff --git a/Source/WebKit/WebProcess/Network/WebSocketChannel.cpp b/Source/WebKit/WebProcess/Network/WebSocketChannel.cpp
index ad343fd..7d9f022 100644
--- a/Source/WebKit/WebProcess/Network/WebSocketChannel.cpp
+++ b/Source/WebKit/WebProcess/Network/WebSocketChannel.cpp
@@ -56,10 +56,9 @@
 
 NetworkSendQueue WebSocketChannel::createMessageQueue(Document& document, WebSocketChannel& channel)
 {
-    return { document, [&channel](auto& string) {
-        auto byteLength = string.sizeInBytes();
-        channel.notifySendFrame(WebSocketFrame::OpCode::OpCodeText, string.utf8().data(), byteLength);
-        channel.sendMessage(Messages::NetworkSocketChannel::SendString { string }, byteLength);
+    return { document, [&channel](auto& utf8String) {
+        channel.notifySendFrame(WebSocketFrame::OpCode::OpCodeText, utf8String.data(), utf8String.length());
+        channel.sendMessage(Messages::NetworkSocketChannel::SendString { IPC::DataReference { reinterpret_cast<const uint8_t*>(utf8String.data()), utf8String.length() } }, utf8String.length());
     }, [&channel](const char* data, size_t byteLength) {
         channel.notifySendFrame(WebSocketFrame::OpCode::OpCodeBinary, data, byteLength);
         channel.sendMessage(Messages::NetworkSocketChannel::SendData { IPC::DataReference { reinterpret_cast<const uint8_t*>(data), byteLength } }, byteLength);
@@ -162,11 +161,11 @@
 
 WebSocketChannel::SendResult WebSocketChannel::send(const String& message)
 {
-    auto byteLength = message.sizeInBytes();
-    if (!increaseBufferedAmount(byteLength))
+    auto utf8 = message.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD);
+    if (!increaseBufferedAmount(utf8.length()))
         return SendFail;
 
-    m_messageQueue.enqueue(message);
+    m_messageQueue.enqueue(WTFMove(utf8));
     return SendSuccess;
 }