2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include "modules/websockets/DocumentWebSocketChannel.h"
34 #include "core/dom/DOMArrayBuffer.h"
35 #include "core/dom/Document.h"
36 #include "core/dom/ExecutionContext.h"
37 #include "core/fetch/UniqueIdentifier.h"
38 #include "core/fileapi/FileReaderLoader.h"
39 #include "core/fileapi/FileReaderLoaderClient.h"
40 #include "core/frame/LocalFrame.h"
41 #include "core/inspector/ConsoleMessage.h"
42 #include "core/inspector/InspectorInstrumentation.h"
43 #include "core/loader/FrameLoader.h"
44 #include "core/loader/FrameLoaderClient.h"
45 #include "core/loader/MixedContentChecker.h"
46 #include "modules/websockets/InspectorWebSocketEvents.h"
47 #include "modules/websockets/WebSocketChannelClient.h"
48 #include "modules/websockets/WebSocketFrame.h"
49 #include "platform/Logging.h"
50 #include "platform/network/WebSocketHandshakeRequest.h"
51 #include "platform/weborigin/SecurityOrigin.h"
52 #include "public/platform/Platform.h"
53 #include "public/platform/WebSecurityOrigin.h"
54 #include "public/platform/WebSocketHandshakeRequestInfo.h"
55 #include "public/platform/WebSocketHandshakeResponseInfo.h"
56 #include "public/platform/WebString.h"
57 #include "public/platform/WebURL.h"
58 #include "public/platform/WebVector.h"
60 using blink::WebSocketHandle
;
64 class DocumentWebSocketChannel::BlobLoader final
: public GarbageCollectedFinalized
<DocumentWebSocketChannel::BlobLoader
>, public FileReaderLoaderClient
{
66 BlobLoader(PassRefPtr
<BlobDataHandle
>, DocumentWebSocketChannel
*);
67 ~BlobLoader() override
{ }
71 // FileReaderLoaderClient functions.
72 void didStartLoading() override
{ }
73 void didReceiveData() override
{ }
74 void didFinishLoading() override
;
75 void didFail(FileError::ErrorCode
) override
;
79 visitor
->trace(m_channel
);
83 Member
<DocumentWebSocketChannel
> m_channel
;
84 FileReaderLoader m_loader
;
87 DocumentWebSocketChannel::BlobLoader::BlobLoader(PassRefPtr
<BlobDataHandle
> blobDataHandle
, DocumentWebSocketChannel
* channel
)
89 , m_loader(FileReaderLoader::ReadAsArrayBuffer
, this)
91 m_loader
.start(channel
->executionContext(), blobDataHandle
);
94 void DocumentWebSocketChannel::BlobLoader::cancel()
97 // didFail will be called immediately.
98 // |this| is deleted here.
101 void DocumentWebSocketChannel::BlobLoader::didFinishLoading()
103 m_channel
->didFinishLoadingBlob(m_loader
.arrayBufferResult());
104 // |this| is deleted here.
107 void DocumentWebSocketChannel::BlobLoader::didFail(FileError::ErrorCode errorCode
)
109 m_channel
->didFailLoadingBlob(errorCode
);
110 // |this| is deleted here.
113 DocumentWebSocketChannel::DocumentWebSocketChannel(ExecutionContext
* context
, WebSocketChannelClient
* client
, const String
& sourceURL
, unsigned lineNumber
, WebSocketHandle
*handle
)
114 : ContextLifecycleObserver(context
)
115 , m_handle(adoptPtr(handle
? handle
: Platform::current()->createWebSocketHandle()))
119 , m_receivedDataSizeForFlowControl(receivedDataSizeForFlowControlHighWaterMark
* 2) // initial quota
120 , m_sentSizeOfTopMessage(0)
121 , m_sourceURLAtConstruction(sourceURL
)
122 , m_lineNumberAtConstruction(lineNumber
)
124 if (context
->isDocument())
125 m_identifier
= createUniqueIdentifier();
128 DocumentWebSocketChannel::~DocumentWebSocketChannel()
130 ASSERT(!m_blobLoader
);
133 bool DocumentWebSocketChannel::connect(const KURL
& url
, const String
& protocol
)
135 WTF_LOG(Network
, "DocumentWebSocketChannel %p connect()", this);
139 if (executionContext()->isDocument() && document()->frame()) {
140 if (MixedContentChecker::shouldBlockWebSocket(document()->frame(), url
))
143 if (MixedContentChecker::isMixedContent(document()->securityOrigin(), url
)) {
144 String message
= "Connecting to a non-secure WebSocket server from a secure origin is deprecated.";
145 document()->addConsoleMessage(ConsoleMessage::create(JSMessageSource
, WarningMessageLevel
, message
));
149 Vector
<String
> protocols
;
150 // Avoid placing an empty token in the Vector when the protocol string is
152 if (!protocol
.isEmpty()) {
153 // Since protocol is already verified and escaped, we can simply split
155 protocol
.split(", ", true, protocols
);
157 WebVector
<WebString
> webProtocols(protocols
.size());
158 for (size_t i
= 0; i
< protocols
.size(); ++i
) {
159 webProtocols
[i
] = protocols
[i
];
162 if (executionContext()->isDocument() && document()->frame())
163 document()->frame()->loader().client()->dispatchWillOpenWebSocket(m_handle
.get());
164 m_handle
->connect(url
, webProtocols
, WebSecurityOrigin(executionContext()->securityOrigin()), this);
166 flowControlIfNecessary();
168 TRACE_EVENT_INSTANT1("devtools.timeline", "WebSocketCreate", TRACE_EVENT_SCOPE_THREAD
, "data", InspectorWebSocketCreateEvent::data(document(), m_identifier
, url
, protocol
));
169 InspectorInstrumentation::didCreateWebSocket(document(), m_identifier
, url
, protocol
);
174 void DocumentWebSocketChannel::send(const CString
& message
)
176 WTF_LOG(Network
, "DocumentWebSocketChannel %p sendText(%s)", this, message
.data());
178 // FIXME: Change the inspector API to show the entire message instead
179 // of individual frames.
180 InspectorInstrumentation::didSendWebSocketFrame(document(), m_identifier
, WebSocketFrame::OpCodeText
, true, message
.data(), message
.length());
182 m_messages
.append(adoptPtr(new Message(message
)));
186 void DocumentWebSocketChannel::send(PassRefPtr
<BlobDataHandle
> blobDataHandle
)
188 WTF_LOG(Network
, "DocumentWebSocketChannel %p sendBlob(%s, %s, %llu)", this, blobDataHandle
->uuid().utf8().data(), blobDataHandle
->type().utf8().data(), blobDataHandle
->size());
190 // FIXME: Change the inspector API to show the entire message instead
191 // of individual frames.
192 // FIXME: We can't access the data here.
193 // Since Binary data are not displayed in Inspector, this does not
194 // affect actual behavior.
195 InspectorInstrumentation::didSendWebSocketFrame(document(), m_identifier
, WebSocketFrame::OpCodeBinary
, true, "", 0);
197 m_messages
.append(adoptPtr(new Message(blobDataHandle
)));
201 void DocumentWebSocketChannel::send(const DOMArrayBuffer
& buffer
, unsigned byteOffset
, unsigned byteLength
)
203 WTF_LOG(Network
, "DocumentWebSocketChannel %p sendArrayBuffer(%p, %u, %u)", this, buffer
.data(), byteOffset
, byteLength
);
205 // FIXME: Change the inspector API to show the entire message instead
206 // of individual frames.
207 InspectorInstrumentation::didSendWebSocketFrame(document(), m_identifier
, WebSocketFrame::OpCodeBinary
, true, static_cast<const char*>(buffer
.data()) + byteOffset
, byteLength
);
209 // buffer.slice copies its contents.
210 // FIXME: Reduce copy by sending the data immediately when we don't need to
212 m_messages
.append(adoptPtr(new Message(buffer
.slice(byteOffset
, byteOffset
+ byteLength
))));
216 void DocumentWebSocketChannel::sendTextAsCharVector(PassOwnPtr
<Vector
<char>> data
)
218 WTF_LOG(Network
, "DocumentWebSocketChannel %p sendTextAsCharVector(%p, %llu)", this, data
.get(), static_cast<unsigned long long>(data
->size()));
220 // FIXME: Change the inspector API to show the entire message instead
221 // of individual frames.
222 InspectorInstrumentation::didSendWebSocketFrame(document(), m_identifier
, WebSocketFrame::OpCodeText
, true, data
->data(), data
->size());
224 m_messages
.append(adoptPtr(new Message(data
, MessageTypeTextAsCharVector
)));
228 void DocumentWebSocketChannel::sendBinaryAsCharVector(PassOwnPtr
<Vector
<char>> data
)
230 WTF_LOG(Network
, "DocumentWebSocketChannel %p sendBinaryAsCharVector(%p, %llu)", this, data
.get(), static_cast<unsigned long long>(data
->size()));
232 // FIXME: Change the inspector API to show the entire message instead
233 // of individual frames.
234 InspectorInstrumentation::didSendWebSocketFrame(document(), m_identifier
, WebSocketFrame::OpCodeBinary
, true, data
->data(), data
->size());
236 m_messages
.append(adoptPtr(new Message(data
, MessageTypeBinaryAsCharVector
)));
240 void DocumentWebSocketChannel::close(int code
, const String
& reason
)
242 WTF_LOG(Network
, "DocumentWebSocketChannel %p close(%d, %s)", this, code
, reason
.utf8().data());
244 unsigned short codeToSend
= static_cast<unsigned short>(code
== CloseEventCodeNotSpecified
? CloseEventCodeNoStatusRcvd
: code
);
245 m_messages
.append(adoptPtr(new Message(codeToSend
, reason
)));
249 void DocumentWebSocketChannel::fail(const String
& reason
, MessageLevel level
, const String
& sourceURL
, unsigned lineNumber
)
251 WTF_LOG(Network
, "DocumentWebSocketChannel %p fail(%s)", this, reason
.utf8().data());
252 // m_handle and m_client can be null here.
255 InspectorInstrumentation::didReceiveWebSocketFrameError(document(), m_identifier
, reason
);
256 const String message
= "WebSocket connection to '" + m_url
.elidedString() + "' failed: " + reason
;
257 executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessageSource
, level
, message
, sourceURL
, lineNumber
));
260 m_client
->didError();
261 // |reason| is only for logging and should not be provided for scripts,
262 // hence close reason must be empty.
263 handleDidClose(false, CloseEventCodeAbnormalClosure
, String());
264 // handleDidClose may delete this object.
267 void DocumentWebSocketChannel::disconnect()
269 WTF_LOG(Network
, "DocumentWebSocketChannel %p disconnect()", this);
271 TRACE_EVENT_INSTANT1("devtools.timeline", "WebSocketDestroy", TRACE_EVENT_SCOPE_THREAD
, "data", InspectorWebSocketEvent::data(document(), m_identifier
));
272 InspectorInstrumentation::didCloseWebSocket(document(), m_identifier
);
274 abortAsyncOperations();
280 DocumentWebSocketChannel::Message::Message(const CString
& text
)
281 : type(MessageTypeText
)
284 DocumentWebSocketChannel::Message::Message(PassRefPtr
<BlobDataHandle
> blobDataHandle
)
285 : type(MessageTypeBlob
)
286 , blobDataHandle(blobDataHandle
) { }
288 DocumentWebSocketChannel::Message::Message(PassRefPtr
<DOMArrayBuffer
> arrayBuffer
)
289 : type(MessageTypeArrayBuffer
)
290 , arrayBuffer(arrayBuffer
) { }
292 DocumentWebSocketChannel::Message::Message(PassOwnPtr
<Vector
<char>> vectorData
, MessageType type
)
294 , vectorData(vectorData
)
296 ASSERT(type
== MessageTypeTextAsCharVector
|| type
== MessageTypeBinaryAsCharVector
);
299 DocumentWebSocketChannel::Message::Message(unsigned short code
, const String
& reason
)
300 : type(MessageTypeClose
)
304 void DocumentWebSocketChannel::sendInternal(WebSocketHandle::MessageType messageType
, const char* data
, size_t totalSize
, uint64_t* consumedBufferedAmount
)
306 WebSocketHandle::MessageType frameType
=
307 m_sentSizeOfTopMessage
? WebSocketHandle::MessageTypeContinuation
: messageType
;
308 ASSERT(totalSize
>= m_sentSizeOfTopMessage
);
309 // The first cast is safe since the result of min() never exceeds
310 // the range of size_t. The second cast is necessary to compile
312 size_t size
= static_cast<size_t>(std::min(m_sendingQuota
, static_cast<uint64_t>(totalSize
- m_sentSizeOfTopMessage
)));
313 bool final
= (m_sentSizeOfTopMessage
+ size
== totalSize
);
315 m_handle
->send(final
, frameType
, data
+ m_sentSizeOfTopMessage
, size
);
317 m_sentSizeOfTopMessage
+= size
;
318 m_sendingQuota
-= size
;
319 *consumedBufferedAmount
+= size
;
322 m_messages
.removeFirst();
323 m_sentSizeOfTopMessage
= 0;
327 void DocumentWebSocketChannel::processSendQueue()
330 uint64_t consumedBufferedAmount
= 0;
331 while (!m_messages
.isEmpty() && !m_blobLoader
) {
332 Message
* message
= m_messages
.first().get();
333 if (m_sendingQuota
== 0 && message
->type
!= MessageTypeClose
)
335 switch (message
->type
) {
336 case MessageTypeText
:
337 sendInternal(WebSocketHandle::MessageTypeText
, message
->text
.data(), message
->text
.length(), &consumedBufferedAmount
);
339 case MessageTypeBlob
:
340 ASSERT(!m_blobLoader
);
341 m_blobLoader
= new BlobLoader(message
->blobDataHandle
, this);
343 case MessageTypeArrayBuffer
:
344 sendInternal(WebSocketHandle::MessageTypeBinary
, static_cast<const char*>(message
->arrayBuffer
->data()), message
->arrayBuffer
->byteLength(), &consumedBufferedAmount
);
346 case MessageTypeTextAsCharVector
:
347 sendInternal(WebSocketHandle::MessageTypeText
, message
->vectorData
->data(), message
->vectorData
->size(), &consumedBufferedAmount
);
349 case MessageTypeBinaryAsCharVector
:
350 sendInternal(WebSocketHandle::MessageTypeBinary
, message
->vectorData
->data(), message
->vectorData
->size(), &consumedBufferedAmount
);
352 case MessageTypeClose
: {
353 // No message should be sent from now on.
354 ASSERT(m_messages
.size() == 1);
355 ASSERT(m_sentSizeOfTopMessage
== 0);
356 m_handle
->close(message
->code
, message
->reason
);
357 m_messages
.removeFirst();
362 if (m_client
&& consumedBufferedAmount
> 0)
363 m_client
->didConsumeBufferedAmount(consumedBufferedAmount
);
366 void DocumentWebSocketChannel::flowControlIfNecessary()
368 if (!m_handle
|| m_receivedDataSizeForFlowControl
< receivedDataSizeForFlowControlHighWaterMark
) {
371 m_handle
->flowControl(m_receivedDataSizeForFlowControl
);
372 m_receivedDataSizeForFlowControl
= 0;
375 void DocumentWebSocketChannel::abortAsyncOperations()
378 m_blobLoader
->cancel();
379 m_blobLoader
.clear();
383 void DocumentWebSocketChannel::handleDidClose(bool wasClean
, unsigned short code
, const String
& reason
)
386 abortAsyncOperations();
390 WebSocketChannelClient
* client
= m_client
;
392 WebSocketChannelClient::ClosingHandshakeCompletionStatus status
=
393 wasClean
? WebSocketChannelClient::ClosingHandshakeComplete
: WebSocketChannelClient::ClosingHandshakeIncomplete
;
394 client
->didClose(status
, code
, reason
);
395 // client->didClose may delete this object.
398 Document
* DocumentWebSocketChannel::document()
400 ASSERT(m_identifier
);
401 ExecutionContext
* context
= executionContext();
402 ASSERT(context
->isDocument());
403 return toDocument(context
);
406 void DocumentWebSocketChannel::didConnect(WebSocketHandle
* handle
, const WebString
& selectedProtocol
, const WebString
& extensions
)
408 WTF_LOG(Network
, "DocumentWebSocketChannel %p didConnect(%p, %s, %s)", this, handle
, selectedProtocol
.utf8().c_str(), extensions
.utf8().c_str());
411 ASSERT(handle
== m_handle
);
414 m_client
->didConnect(selectedProtocol
, extensions
);
417 void DocumentWebSocketChannel::didStartOpeningHandshake(WebSocketHandle
* handle
, const WebSocketHandshakeRequestInfo
& request
)
419 WTF_LOG(Network
, "DocumentWebSocketChannel %p didStartOpeningHandshake(%p)", this, handle
);
422 ASSERT(handle
== m_handle
);
425 TRACE_EVENT_INSTANT1("devtools.timeline", "WebSocketSendHandshakeRequest", TRACE_EVENT_SCOPE_THREAD
, "data", InspectorWebSocketEvent::data(document(), m_identifier
));
426 InspectorInstrumentation::willSendWebSocketHandshakeRequest(document(), m_identifier
, &request
.toCoreRequest());
427 m_handshakeRequest
= WebSocketHandshakeRequest::create(request
.toCoreRequest());
431 void DocumentWebSocketChannel::didFinishOpeningHandshake(WebSocketHandle
* handle
, const WebSocketHandshakeResponseInfo
& response
)
433 WTF_LOG(Network
, "DocumentWebSocketChannel %p didFinishOpeningHandshake(%p)", this, handle
);
436 ASSERT(handle
== m_handle
);
439 TRACE_EVENT_INSTANT1("devtools.timeline", "WebSocketReceiveHandshakeResponse", TRACE_EVENT_SCOPE_THREAD
, "data", InspectorWebSocketEvent::data(document(), m_identifier
));
440 InspectorInstrumentation::didReceiveWebSocketHandshakeResponse(document(), m_identifier
, m_handshakeRequest
.get(), &response
.toCoreResponse());
442 m_handshakeRequest
.clear();
445 void DocumentWebSocketChannel::didFail(WebSocketHandle
* handle
, const WebString
& message
)
447 WTF_LOG(Network
, "DocumentWebSocketChannel %p didFail(%p, %s)", this, handle
, message
.utf8().data());
450 ASSERT(handle
== m_handle
);
452 // This function is called when the browser is required to fail the
453 // WebSocketConnection. Hence we fail this channel by calling
454 // |this->failAsError| function.
455 failAsError(message
);
456 // |this| may be deleted.
459 void DocumentWebSocketChannel::didReceiveData(WebSocketHandle
* handle
, bool fin
, WebSocketHandle::MessageType type
, const char* data
, size_t size
)
461 WTF_LOG(Network
, "DocumentWebSocketChannel %p didReceiveData(%p, %d, %d, (%p, %zu))", this, handle
, fin
, type
, data
, size
);
464 ASSERT(handle
== m_handle
);
466 // Non-final frames cannot be empty.
470 case WebSocketHandle::MessageTypeText
:
471 ASSERT(m_receivingMessageData
.isEmpty());
472 m_receivingMessageTypeIsText
= true;
474 case WebSocketHandle::MessageTypeBinary
:
475 ASSERT(m_receivingMessageData
.isEmpty());
476 m_receivingMessageTypeIsText
= false;
478 case WebSocketHandle::MessageTypeContinuation
:
479 ASSERT(!m_receivingMessageData
.isEmpty());
483 m_receivingMessageData
.append(data
, size
);
484 m_receivedDataSizeForFlowControl
+= size
;
485 flowControlIfNecessary();
490 // FIXME: Change the inspector API to show the entire message instead
491 // of individual frames.
492 WebSocketFrame::OpCode opcode
= m_receivingMessageTypeIsText
? WebSocketFrame::OpCodeText
: WebSocketFrame::OpCodeBinary
;
493 WebSocketFrame
frame(opcode
, m_receivingMessageData
.data(), m_receivingMessageData
.size(), WebSocketFrame::Final
);
494 InspectorInstrumentation::didReceiveWebSocketFrame(document(), m_identifier
, frame
.opCode
, frame
.masked
, frame
.payload
, frame
.payloadLength
);
496 if (m_receivingMessageTypeIsText
) {
497 String message
= m_receivingMessageData
.isEmpty() ? emptyString() : String::fromUTF8(m_receivingMessageData
.data(), m_receivingMessageData
.size());
498 m_receivingMessageData
.clear();
499 if (message
.isNull()) {
500 failAsError("Could not decode a text frame as UTF-8.");
501 // failAsError may delete this object.
503 m_client
->didReceiveTextMessage(message
);
506 OwnPtr
<Vector
<char>> binaryData
= adoptPtr(new Vector
<char>);
507 binaryData
->swap(m_receivingMessageData
);
508 m_client
->didReceiveBinaryMessage(binaryData
.release());
512 void DocumentWebSocketChannel::didClose(WebSocketHandle
* handle
, bool wasClean
, unsigned short code
, const WebString
& reason
)
514 WTF_LOG(Network
, "DocumentWebSocketChannel %p didClose(%p, %d, %u, %s)", this, handle
, wasClean
, code
, String(reason
).utf8().data());
517 ASSERT(handle
== m_handle
);
522 TRACE_EVENT_INSTANT1("devtools.timeline", "WebSocketDestroy", TRACE_EVENT_SCOPE_THREAD
, "data", InspectorWebSocketEvent::data(document(), m_identifier
));
523 InspectorInstrumentation::didCloseWebSocket(document(), m_identifier
);
527 handleDidClose(wasClean
, code
, reason
);
528 // handleDidClose may delete this object.
531 void DocumentWebSocketChannel::didReceiveFlowControl(WebSocketHandle
* handle
, int64_t quota
)
533 WTF_LOG(Network
, "DocumentWebSocketChannel %p didReceiveFlowControl(%p, %ld)", this, handle
, static_cast<long>(quota
));
536 ASSERT(handle
== m_handle
);
539 m_sendingQuota
+= quota
;
543 void DocumentWebSocketChannel::didStartClosingHandshake(WebSocketHandle
* handle
)
545 WTF_LOG(Network
, "DocumentWebSocketChannel %p didStartClosingHandshake(%p)", this, handle
);
548 ASSERT(handle
== m_handle
);
551 m_client
->didStartClosingHandshake();
554 void DocumentWebSocketChannel::didFinishLoadingBlob(PassRefPtr
<DOMArrayBuffer
> buffer
)
556 m_blobLoader
.clear();
558 // The loaded blob is always placed on m_messages[0].
559 ASSERT(m_messages
.size() > 0 && m_messages
.first()->type
== MessageTypeBlob
);
560 // We replace it with the loaded blob.
561 m_messages
.first() = adoptPtr(new Message(buffer
));
565 void DocumentWebSocketChannel::didFailLoadingBlob(FileError::ErrorCode errorCode
)
567 m_blobLoader
.clear();
568 if (errorCode
== FileError::ABORT_ERR
) {
569 // The error is caused by cancel().
572 // FIXME: Generate human-friendly reason message.
573 failAsError("Failed to load Blob: error code = " + String::number(errorCode
));
574 // |this| can be deleted here.
577 DEFINE_TRACE(DocumentWebSocketChannel
)
579 visitor
->trace(m_blobLoader
);
580 visitor
->trace(m_client
);
581 WebSocketChannel::trace(visitor
);
582 ContextLifecycleObserver::trace(visitor
);