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/NewWebSocketChannelImpl.h"
34 #include "core/dom/Document.h"
35 #include "core/dom/ExecutionContext.h"
36 #include "core/fileapi/FileReaderLoader.h"
37 #include "core/fileapi/FileReaderLoaderClient.h"
38 #include "core/frame/LocalFrame.h"
39 #include "core/inspector/ConsoleMessage.h"
40 #include "core/inspector/InspectorInstrumentation.h"
41 #include "core/inspector/InspectorTraceEvents.h"
42 #include "core/loader/FrameLoader.h"
43 #include "core/loader/FrameLoaderClient.h"
44 #include "core/loader/MixedContentChecker.h"
45 #include "core/loader/UniqueIdentifier.h"
46 #include "modules/websockets/WebSocketChannelClient.h"
47 #include "modules/websockets/WebSocketFrame.h"
48 #include "platform/Logging.h"
49 #include "platform/network/WebSocketHandshakeRequest.h"
50 #include "platform/weborigin/SecurityOrigin.h"
51 #include "public/platform/Platform.h"
52 #include "public/platform/WebSerializedOrigin.h"
53 #include "public/platform/WebSocketHandshakeRequestInfo.h"
54 #include "public/platform/WebSocketHandshakeResponseInfo.h"
55 #include "public/platform/WebString.h"
56 #include "public/platform/WebURL.h"
57 #include "public/platform/WebVector.h"
59 using blink::WebSocketHandle;
63 class NewWebSocketChannelImpl::BlobLoader FINAL : public GarbageCollectedFinalized<NewWebSocketChannelImpl::BlobLoader>, public FileReaderLoaderClient {
65 BlobLoader(PassRefPtr<BlobDataHandle>, NewWebSocketChannelImpl*);
66 virtual ~BlobLoader() { }
70 // FileReaderLoaderClient functions.
71 virtual void didStartLoading() OVERRIDE { }
72 virtual void didReceiveData() OVERRIDE { }
73 virtual void didFinishLoading() OVERRIDE;
74 virtual void didFail(FileError::ErrorCode) OVERRIDE;
76 void trace(Visitor* visitor)
78 visitor->trace(m_channel);
82 Member<NewWebSocketChannelImpl> m_channel;
83 FileReaderLoader m_loader;
86 NewWebSocketChannelImpl::BlobLoader::BlobLoader(PassRefPtr<BlobDataHandle> blobDataHandle, NewWebSocketChannelImpl* channel)
88 , m_loader(FileReaderLoader::ReadAsArrayBuffer, this)
90 m_loader.start(channel->executionContext(), blobDataHandle);
93 void NewWebSocketChannelImpl::BlobLoader::cancel()
96 // didFail will be called immediately.
97 // |this| is deleted here.
100 void NewWebSocketChannelImpl::BlobLoader::didFinishLoading()
102 m_channel->didFinishLoadingBlob(m_loader.arrayBufferResult());
103 // |this| is deleted here.
106 void NewWebSocketChannelImpl::BlobLoader::didFail(FileError::ErrorCode errorCode)
108 m_channel->didFailLoadingBlob(errorCode);
109 // |this| is deleted here.
112 NewWebSocketChannelImpl::NewWebSocketChannelImpl(ExecutionContext* context, WebSocketChannelClient* client, const String& sourceURL, unsigned lineNumber, WebSocketHandle *handle)
113 : ContextLifecycleObserver(context)
114 , m_handle(adoptPtr(handle ? handle : Platform::current()->createWebSocketHandle()))
118 , m_receivedDataSizeForFlowControl(receivedDataSizeForFlowControlHighWaterMark * 2) // initial quota
119 , m_sentSizeOfTopMessage(0)
120 , m_sourceURLAtConstruction(sourceURL)
121 , m_lineNumberAtConstruction(lineNumber)
123 if (context->isDocument() && toDocument(context)->page())
124 m_identifier = createUniqueIdentifier();
127 NewWebSocketChannelImpl::~NewWebSocketChannelImpl()
129 ASSERT(!m_blobLoader);
132 bool NewWebSocketChannelImpl::connect(const KURL& url, const String& protocol)
134 WTF_LOG(Network, "NewWebSocketChannelImpl %p connect()", this);
138 if (executionContext()->isDocument() && document()->frame()) {
139 if (!document()->frame()->loader().mixedContentChecker()->canConnectInsecureWebSocket(document()->securityOrigin(), url))
142 if (MixedContentChecker::isMixedContent(document()->securityOrigin(), url)) {
143 String message = "Connecting to a non-secure WebSocket server from a secure origin is deprecated.";
144 document()->addConsoleMessage(ConsoleMessage::create(JSMessageSource, WarningMessageLevel, message));
148 Vector<String> protocols;
149 // Avoid placing an empty token in the Vector when the protocol string is
151 if (!protocol.isEmpty()) {
152 // Since protocol is already verified and escaped, we can simply split
154 protocol.split(", ", true, protocols);
156 WebVector<WebString> webProtocols(protocols.size());
157 for (size_t i = 0; i < protocols.size(); ++i) {
158 webProtocols[i] = protocols[i];
161 if (executionContext()->isDocument() && document()->frame())
162 document()->frame()->loader().client()->dispatchWillOpenWebSocket(m_handle.get());
163 m_handle->connect(url, webProtocols, *executionContext()->securityOrigin(), this);
165 flowControlIfNecessary();
167 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "WebSocketCreate", "data", InspectorWebSocketCreateEvent::data(document(), m_identifier, url, protocol));
168 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", "stack", InspectorCallStackEvent::currentCallStack());
169 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing.
170 InspectorInstrumentation::didCreateWebSocket(document(), m_identifier, url, protocol);
175 void NewWebSocketChannelImpl::send(const String& message)
177 WTF_LOG(Network, "NewWebSocketChannelImpl %p sendText(%s)", this, message.utf8().data());
179 // FIXME: Change the inspector API to show the entire message instead
180 // of individual frames.
181 CString data = message.utf8();
182 InspectorInstrumentation::didSendWebSocketFrame(document(), m_identifier, WebSocketFrame::OpCodeText, true, data.data(), data.length());
184 m_messages.append(adoptPtr(new Message(message)));
188 void NewWebSocketChannelImpl::send(PassRefPtr<BlobDataHandle> blobDataHandle)
190 WTF_LOG(Network, "NewWebSocketChannelImpl %p sendBlob(%s, %s, %llu)", this, blobDataHandle->uuid().utf8().data(), blobDataHandle->type().utf8().data(), blobDataHandle->size());
192 // FIXME: Change the inspector API to show the entire message instead
193 // of individual frames.
194 // FIXME: We can't access the data here.
195 // Since Binary data are not displayed in Inspector, this does not
196 // affect actual behavior.
197 InspectorInstrumentation::didSendWebSocketFrame(document(), m_identifier, WebSocketFrame::OpCodeBinary, true, "", 0);
199 m_messages.append(adoptPtr(new Message(blobDataHandle)));
203 void NewWebSocketChannelImpl::send(const ArrayBuffer& buffer, unsigned byteOffset, unsigned byteLength)
205 WTF_LOG(Network, "NewWebSocketChannelImpl %p sendArrayBuffer(%p, %u, %u)", this, buffer.data(), byteOffset, byteLength);
207 // FIXME: Change the inspector API to show the entire message instead
208 // of individual frames.
209 InspectorInstrumentation::didSendWebSocketFrame(document(), m_identifier, WebSocketFrame::OpCodeBinary, true, static_cast<const char*>(buffer.data()) + byteOffset, byteLength);
211 // buffer.slice copies its contents.
212 // FIXME: Reduce copy by sending the data immediately when we don't need to
214 m_messages.append(adoptPtr(new Message(buffer.slice(byteOffset, byteOffset + byteLength))));
218 void NewWebSocketChannelImpl::send(PassOwnPtr<Vector<char> > data)
220 WTF_LOG(Network, "NewWebSocketChannelImpl %p sendVector(%p, %llu)", this, data.get(), static_cast<unsigned long long>(data->size()));
222 // FIXME: Change the inspector API to show the entire message instead
223 // of individual frames.
224 InspectorInstrumentation::didSendWebSocketFrame(document(), m_identifier, WebSocketFrame::OpCodeBinary, true, data->data(), data->size());
226 m_messages.append(adoptPtr(new Message(data)));
230 void NewWebSocketChannelImpl::close(int code, const String& reason)
232 WTF_LOG(Network, "NewWebSocketChannelImpl %p close(%d, %s)", this, code, reason.utf8().data());
234 unsigned short codeToSend = static_cast<unsigned short>(code == CloseEventCodeNotSpecified ? CloseEventCodeNoStatusRcvd : code);
235 m_messages.append(adoptPtr(new Message(codeToSend, reason)));
239 void NewWebSocketChannelImpl::fail(const String& reason, MessageLevel level, const String& sourceURL, unsigned lineNumber)
241 WTF_LOG(Network, "NewWebSocketChannelImpl %p fail(%s)", this, reason.utf8().data());
242 // m_handle and m_client can be null here.
245 InspectorInstrumentation::didReceiveWebSocketFrameError(document(), m_identifier, reason);
246 const String message = "WebSocket connection to '" + m_url.elidedString() + "' failed: " + reason;
247 executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessageSource, level, message, sourceURL, lineNumber));
250 m_client->didReceiveMessageError();
251 // |reason| is only for logging and should not be provided for scripts,
252 // hence close reason must be empty.
253 handleDidClose(false, CloseEventCodeAbnormalClosure, String());
254 // handleDidClose may delete this object.
257 void NewWebSocketChannelImpl::disconnect()
259 WTF_LOG(Network, "NewWebSocketChannelImpl %p disconnect()", this);
261 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "WebSocketDestroy", "data", InspectorWebSocketEvent::data(document(), m_identifier));
262 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", "stack", InspectorCallStackEvent::currentCallStack());
263 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing.
264 InspectorInstrumentation::didCloseWebSocket(document(), m_identifier);
266 abortAsyncOperations();
272 void NewWebSocketChannelImpl::suspend()
274 WTF_LOG(Network, "NewWebSocketChannelImpl %p suspend()", this);
277 void NewWebSocketChannelImpl::resume()
279 WTF_LOG(Network, "NewWebSocketChannelImpl %p resume()", this);
282 NewWebSocketChannelImpl::Message::Message(const String& text)
283 : type(MessageTypeText)
284 , text(text.utf8(StrictUTF8ConversionReplacingUnpairedSurrogatesWithFFFD)) { }
286 NewWebSocketChannelImpl::Message::Message(PassRefPtr<BlobDataHandle> blobDataHandle)
287 : type(MessageTypeBlob)
288 , blobDataHandle(blobDataHandle) { }
290 NewWebSocketChannelImpl::Message::Message(PassRefPtr<ArrayBuffer> arrayBuffer)
291 : type(MessageTypeArrayBuffer)
292 , arrayBuffer(arrayBuffer) { }
294 NewWebSocketChannelImpl::Message::Message(PassOwnPtr<Vector<char> > vectorData)
295 : type(MessageTypeVector)
296 , vectorData(vectorData) { }
298 NewWebSocketChannelImpl::Message::Message(unsigned short code, const String& reason)
299 : type(MessageTypeClose)
303 void NewWebSocketChannelImpl::sendInternal()
306 unsigned long consumedBufferedAmount = 0;
307 while (!m_messages.isEmpty() && !m_blobLoader) {
309 Message* message = m_messages.first().get();
310 if (m_sendingQuota <= 0 && message->type != MessageTypeClose)
312 switch (message->type) {
313 case MessageTypeText: {
314 WebSocketHandle::MessageType type =
315 m_sentSizeOfTopMessage ? WebSocketHandle::MessageTypeContinuation : WebSocketHandle::MessageTypeText;
316 size_t size = std::min(static_cast<size_t>(m_sendingQuota), message->text.length() - m_sentSizeOfTopMessage);
317 final = (m_sentSizeOfTopMessage + size == message->text.length());
318 m_handle->send(final, type, message->text.data() + m_sentSizeOfTopMessage, size);
319 m_sentSizeOfTopMessage += size;
320 m_sendingQuota -= size;
321 consumedBufferedAmount += size;
324 case MessageTypeBlob:
325 ASSERT(!m_blobLoader);
326 m_blobLoader = new BlobLoader(message->blobDataHandle, this);
328 case MessageTypeArrayBuffer: {
329 WebSocketHandle::MessageType type =
330 m_sentSizeOfTopMessage ? WebSocketHandle::MessageTypeContinuation : WebSocketHandle::MessageTypeBinary;
331 size_t size = std::min(static_cast<size_t>(m_sendingQuota), message->arrayBuffer->byteLength() - m_sentSizeOfTopMessage);
332 final = (m_sentSizeOfTopMessage + size == message->arrayBuffer->byteLength());
333 m_handle->send(final, type, static_cast<const char*>(message->arrayBuffer->data()) + m_sentSizeOfTopMessage, size);
334 m_sentSizeOfTopMessage += size;
335 m_sendingQuota -= size;
336 consumedBufferedAmount += size;
339 case MessageTypeVector: {
340 WebSocketHandle::MessageType type =
341 m_sentSizeOfTopMessage ? WebSocketHandle::MessageTypeContinuation : WebSocketHandle::MessageTypeBinary;
342 size_t size = std::min(static_cast<size_t>(m_sendingQuota), message->vectorData->size() - m_sentSizeOfTopMessage);
343 final = (m_sentSizeOfTopMessage + size == message->vectorData->size());
344 m_handle->send(final, type, message->vectorData->data() + m_sentSizeOfTopMessage, size);
345 m_sentSizeOfTopMessage += size;
346 m_sendingQuota -= size;
347 consumedBufferedAmount += size;
350 case MessageTypeClose: {
351 // No message should be sent from now on.
352 ASSERT(m_messages.size() == 1);
353 m_handle->close(message->code, message->reason);
359 m_messages.removeFirst();
360 m_sentSizeOfTopMessage = 0;
363 if (m_client && consumedBufferedAmount > 0)
364 m_client->didConsumeBufferedAmount(consumedBufferedAmount);
367 void NewWebSocketChannelImpl::flowControlIfNecessary()
369 if (!m_handle || m_receivedDataSizeForFlowControl < receivedDataSizeForFlowControlHighWaterMark) {
372 m_handle->flowControl(m_receivedDataSizeForFlowControl);
373 m_receivedDataSizeForFlowControl = 0;
376 void NewWebSocketChannelImpl::abortAsyncOperations()
379 m_blobLoader->cancel();
380 m_blobLoader.clear();
384 void NewWebSocketChannelImpl::handleDidClose(bool wasClean, unsigned short code, const String& reason)
387 abortAsyncOperations();
391 WebSocketChannelClient* client = m_client;
393 WebSocketChannelClient::ClosingHandshakeCompletionStatus status =
394 wasClean ? WebSocketChannelClient::ClosingHandshakeComplete : WebSocketChannelClient::ClosingHandshakeIncomplete;
395 client->didClose(status, code, reason);
396 // client->didClose may delete this object.
399 Document* NewWebSocketChannelImpl::document()
401 ASSERT(m_identifier);
402 ExecutionContext* context = executionContext();
403 ASSERT(context->isDocument());
404 return toDocument(context);
407 void NewWebSocketChannelImpl::didConnect(WebSocketHandle* handle, bool fail, const WebString& selectedProtocol, const WebString& extensions)
409 WTF_LOG(Network, "NewWebSocketChannelImpl %p didConnect(%p, %d, %s, %s)", this, handle, fail, selectedProtocol.utf8().data(), extensions.utf8().data());
411 ASSERT(handle == m_handle);
414 failAsError("Cannot connect to " + m_url.string() + ".");
415 // failAsError may delete this object.
418 m_client->didConnect(selectedProtocol, extensions);
421 void NewWebSocketChannelImpl::didStartOpeningHandshake(WebSocketHandle* handle, const WebSocketHandshakeRequestInfo& request)
423 WTF_LOG(Network, "NewWebSocketChannelImpl %p didStartOpeningHandshake(%p)", this, handle);
425 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "WebSocketSendHandshakeRequest", "data", InspectorWebSocketEvent::data(document(), m_identifier));
426 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", "stack", InspectorCallStackEvent::currentCallStack());
427 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing.
428 InspectorInstrumentation::willSendWebSocketHandshakeRequest(document(), m_identifier, &request.toCoreRequest());
429 m_handshakeRequest = WebSocketHandshakeRequest::create(request.toCoreRequest());
433 void NewWebSocketChannelImpl::didFinishOpeningHandshake(WebSocketHandle* handle, const WebSocketHandshakeResponseInfo& response)
435 WTF_LOG(Network, "NewWebSocketChannelImpl %p didFinishOpeningHandshake(%p)", this, handle);
437 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "WebSocketReceiveHandshakeResponse", "data", InspectorWebSocketEvent::data(document(), m_identifier));
438 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing.
439 InspectorInstrumentation::didReceiveWebSocketHandshakeResponse(document(), m_identifier, m_handshakeRequest.get(), &response.toCoreResponse());
441 m_handshakeRequest.clear();
444 void NewWebSocketChannelImpl::didFail(WebSocketHandle* handle, const WebString& message)
446 WTF_LOG(Network, "NewWebSocketChannelImpl %p didFail(%p, %s)", this, handle, message.utf8().data());
447 // This function is called when the browser is required to fail the
448 // WebSocketConnection. Hence we fail this channel by calling
449 // |this->failAsError| function.
450 failAsError(message);
451 // |this| may be deleted.
454 void NewWebSocketChannelImpl::didReceiveData(WebSocketHandle* handle, bool fin, WebSocketHandle::MessageType type, const char* data, size_t size)
456 WTF_LOG(Network, "NewWebSocketChannelImpl %p didReceiveData(%p, %d, %d, (%p, %zu))", this, handle, fin, type, data, size);
458 ASSERT(handle == m_handle);
460 // Non-final frames cannot be empty.
463 case WebSocketHandle::MessageTypeText:
464 ASSERT(m_receivingMessageData.isEmpty());
465 m_receivingMessageTypeIsText = true;
467 case WebSocketHandle::MessageTypeBinary:
468 ASSERT(m_receivingMessageData.isEmpty());
469 m_receivingMessageTypeIsText = false;
471 case WebSocketHandle::MessageTypeContinuation:
472 ASSERT(!m_receivingMessageData.isEmpty());
476 m_receivingMessageData.append(data, size);
477 m_receivedDataSizeForFlowControl += size;
478 flowControlIfNecessary();
483 // FIXME: Change the inspector API to show the entire message instead
484 // of individual frames.
485 WebSocketFrame::OpCode opcode = m_receivingMessageTypeIsText ? WebSocketFrame::OpCodeText : WebSocketFrame::OpCodeBinary;
486 WebSocketFrame frame(opcode, m_receivingMessageData.data(), m_receivingMessageData.size(), WebSocketFrame::Final);
487 InspectorInstrumentation::didReceiveWebSocketFrame(document(), m_identifier, frame.opCode, frame.masked, frame.payload, frame.payloadLength);
489 if (m_receivingMessageTypeIsText) {
490 String message = m_receivingMessageData.isEmpty() ? emptyString() : String::fromUTF8(m_receivingMessageData.data(), m_receivingMessageData.size());
491 m_receivingMessageData.clear();
492 if (message.isNull()) {
493 failAsError("Could not decode a text frame as UTF-8.");
494 // failAsError may delete this object.
496 m_client->didReceiveMessage(message);
499 OwnPtr<Vector<char> > binaryData = adoptPtr(new Vector<char>);
500 binaryData->swap(m_receivingMessageData);
501 m_client->didReceiveBinaryData(binaryData.release());
505 void NewWebSocketChannelImpl::didClose(WebSocketHandle* handle, bool wasClean, unsigned short code, const WebString& reason)
507 WTF_LOG(Network, "NewWebSocketChannelImpl %p didClose(%p, %d, %u, %s)", this, handle, wasClean, code, String(reason).utf8().data());
511 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "WebSocketDestroy", "data", InspectorWebSocketEvent::data(document(), m_identifier));
512 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", "stack", InspectorCallStackEvent::currentCallStack());
513 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing.
514 InspectorInstrumentation::didCloseWebSocket(document(), m_identifier);
518 handleDidClose(wasClean, code, reason);
519 // handleDidClose may delete this object.
522 void NewWebSocketChannelImpl::didReceiveFlowControl(WebSocketHandle* handle, int64_t quota)
524 WTF_LOG(Network, "NewWebSocketChannelImpl %p didReceiveFlowControl(%p, %ld)", this, handle, static_cast<long>(quota));
526 m_sendingQuota += quota;
530 void NewWebSocketChannelImpl::didStartClosingHandshake(WebSocketHandle* handle)
532 WTF_LOG(Network, "NewWebSocketChannelImpl %p didStartClosingHandshake(%p)", this, handle);
534 m_client->didStartClosingHandshake();
537 void NewWebSocketChannelImpl::didFinishLoadingBlob(PassRefPtr<ArrayBuffer> buffer)
539 m_blobLoader.clear();
541 // The loaded blob is always placed on m_messages[0].
542 ASSERT(m_messages.size() > 0 && m_messages.first()->type == MessageTypeBlob);
543 // We replace it with the loaded blob.
544 m_messages.first() = adoptPtr(new Message(buffer));
548 void NewWebSocketChannelImpl::didFailLoadingBlob(FileError::ErrorCode errorCode)
550 m_blobLoader.clear();
551 if (errorCode == FileError::ABORT_ERR) {
552 // The error is caused by cancel().
555 // FIXME: Generate human-friendly reason message.
556 failAsError("Failed to load Blob: error code = " + String::number(errorCode));
557 // |this| can be deleted here.
560 void NewWebSocketChannelImpl::trace(Visitor* visitor)
562 visitor->trace(m_blobLoader);
563 visitor->trace(m_client);
564 WebSocketChannel::trace(visitor);