blob: 72ff04197d69c342a51fc24e4bd04c7fa6c492e7 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/modules/websockets/web_socket_channel_impl.h"
#include <memory>
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_socket_handshake_throttle.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fileapi/file_reader_loader.h"
#include "third_party/blink/renderer/core/fileapi/file_reader_loader_client.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/base_fetch_context.h"
#include "third_party/blink/renderer/core/loader/mixed_content_checker.h"
#include "third_party/blink/renderer/core/loader/subresource_filter.h"
#include "third_party/blink/renderer/core/loader/threadable_loading_context.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/modules/websockets/inspector_web_socket_events.h"
#include "third_party/blink/renderer/modules/websockets/web_socket_channel_client.h"
#include "third_party/blink/renderer/modules/websockets/web_socket_handle_impl.h"
#include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h"
#include "third_party/blink/renderer/platform/network/network_log.h"
#include "third_party/blink/renderer/platform/network/web_socket_handshake_request.h"
#include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
namespace {
enum WebSocketOpCode {
kOpCodeText = 0x1,
kOpCodeBinary = 0x2,
};
} // namespace
class WebSocketChannelImpl::BlobLoader final
: public GarbageCollectedFinalized<WebSocketChannelImpl::BlobLoader>,
public FileReaderLoaderClient {
public:
BlobLoader(scoped_refptr<BlobDataHandle>, WebSocketChannelImpl*);
~BlobLoader() override = default;
void Cancel();
// FileReaderLoaderClient functions.
void DidStartLoading() override {}
void DidReceiveData() override {}
void DidFinishLoading() override;
void DidFail(FileError::ErrorCode) override;
void Trace(blink::Visitor* visitor) { visitor->Trace(channel_); }
private:
Member<WebSocketChannelImpl> channel_;
std::unique_ptr<FileReaderLoader> loader_;
};
class WebSocketChannelImpl::Message
: public GarbageCollectedFinalized<WebSocketChannelImpl::Message> {
public:
explicit Message(const CString&);
explicit Message(scoped_refptr<BlobDataHandle>);
explicit Message(DOMArrayBuffer*);
// For WorkerWebSocketChannel
explicit Message(std::unique_ptr<Vector<char>>, MessageType);
// Close message
Message(unsigned short code, const String& reason);
void Trace(blink::Visitor* visitor) { visitor->Trace(array_buffer); }
MessageType type;
CString text;
scoped_refptr<BlobDataHandle> blob_data_handle;
Member<DOMArrayBuffer> array_buffer;
std::unique_ptr<Vector<char>> vector_data;
unsigned short code;
String reason;
};
WebSocketChannelImpl::BlobLoader::BlobLoader(
scoped_refptr<BlobDataHandle> blob_data_handle,
WebSocketChannelImpl* channel)
: channel_(channel),
loader_(FileReaderLoader::Create(FileReaderLoader::kReadAsArrayBuffer,
this)) {
loader_->Start(std::move(blob_data_handle));
}
void WebSocketChannelImpl::BlobLoader::Cancel() {
loader_->Cancel();
// DidFail will be called immediately.
// |this| is deleted here.
}
void WebSocketChannelImpl::BlobLoader::DidFinishLoading() {
channel_->DidFinishLoadingBlob(loader_->ArrayBufferResult());
// |this| is deleted here.
}
void WebSocketChannelImpl::BlobLoader::DidFail(
FileError::ErrorCode error_code) {
channel_->DidFailLoadingBlob(error_code);
// |this| is deleted here.
}
struct WebSocketChannelImpl::ConnectInfo {
ConnectInfo(const String& selected_protocol, const String& extensions)
: selected_protocol(selected_protocol), extensions(extensions) {}
const String selected_protocol;
const String extensions;
};
// static
WebSocketChannelImpl* WebSocketChannelImpl::CreateForTesting(
Document* document,
WebSocketChannelClient* client,
std::unique_ptr<SourceLocation> location,
WebSocketHandle* handle,
std::unique_ptr<WebSocketHandshakeThrottle> handshake_throttle) {
return new WebSocketChannelImpl(
ThreadableLoadingContext::Create(*document), client, std::move(location),
base::WrapUnique(handle), std::move(handshake_throttle));
}
// static
WebSocketChannelImpl* WebSocketChannelImpl::Create(
ThreadableLoadingContext* loading_context,
WebSocketChannelClient* client,
std::unique_ptr<SourceLocation> location) {
return new WebSocketChannelImpl(
loading_context, client, std::move(location),
std::make_unique<WebSocketHandleImpl>(),
loading_context->GetFetchContext()->CreateWebSocketHandshakeThrottle());
}
WebSocketChannelImpl::WebSocketChannelImpl(
ThreadableLoadingContext* loading_context,
WebSocketChannelClient* client,
std::unique_ptr<SourceLocation> location,
std::unique_ptr<WebSocketHandle> handle,
std::unique_ptr<WebSocketHandshakeThrottle> handshake_throttle)
: handle_(std::move(handle)),
client_(client),
identifier_(CreateUniqueIdentifier()),
loading_context_(loading_context),
sending_quota_(0),
received_data_size_for_flow_control_(0),
sent_size_of_top_message_(0),
location_at_construction_(std::move(location)),
handshake_throttle_(std::move(handshake_throttle)),
throttle_passed_(false) {}
WebSocketChannelImpl::~WebSocketChannelImpl() {
DCHECK(!blob_loader_);
}
bool WebSocketChannelImpl::Connect(
const KURL& url,
const String& protocol,
network::mojom::blink::WebSocketPtr socket_ptr) {
NETWORK_DVLOG(1) << this << " Connect()";
if (!handle_)
return false;
if (loading_context_->GetFetchContext()
->ShouldBlockWebSocketByMixedContentCheck(url)) {
return false;
}
if (auto* scheduler = GetExecutionContext()->GetScheduler())
connection_handle_for_scheduler_ = scheduler->OnActiveConnectionCreated();
if (MixedContentChecker::IsMixedContent(
GetExecutionContext()->GetSecurityOrigin(), url)) {
String message =
"Connecting to a non-secure WebSocket server from a secure origin is "
"deprecated.";
GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kWarningMessageLevel, message));
}
url_ = url;
Vector<String> protocols;
// Avoid placing an empty token in the Vector when the protocol string is
// empty.
if (!protocol.IsEmpty()) {
// Since protocol is already verified and escaped, we can simply split
// it.
protocol.Split(", ", true, protocols);
}
// If the connection needs to be filtered, asynchronously fail. Synchronous
// failure blocks the worker thread which should be avoided. Note that
// returning "true" just indicates that this was not a mixed content error.
if (ShouldDisallowConnection(url)) {
GetExecutionContext()
->GetTaskRunner(TaskType::kNetworking)
->PostTask(FROM_HERE,
WTF::Bind(&WebSocketChannelImpl::TearDownFailedConnection,
WrapPersistent(this)));
return true;
}
handle_->Connect(std::move(socket_ptr), url, protocols,
loading_context_->GetFetchContext()->GetSiteForCookies(),
loading_context_->GetExecutionContext()->UserAgent(), this,
loading_context_->GetExecutionContext()
->GetTaskRunner(TaskType::kNetworking)
.get());
if (handshake_throttle_) {
handshake_throttle_->ThrottleHandshake(url, this);
} else {
// Treat no throttle as success.
throttle_passed_ = true;
}
TRACE_EVENT_INSTANT1("devtools.timeline", "WebSocketCreate",
TRACE_EVENT_SCOPE_THREAD, "data",
InspectorWebSocketCreateEvent::Data(
GetExecutionContext(), identifier_, url, protocol));
probe::didCreateWebSocket(GetExecutionContext(), identifier_, url, protocol);
return true;
}
bool WebSocketChannelImpl::Connect(const KURL& url, const String& protocol) {
network::mojom::blink::WebSocketPtr socket_ptr;
auto socket_request = mojo::MakeRequest(&socket_ptr);
service_manager::InterfaceProvider* interface_provider =
GetExecutionContext()->GetInterfaceProvider();
if (interface_provider)
interface_provider->GetInterface(std::move(socket_request));
return Connect(url, protocol, std::move(socket_ptr));
}
void WebSocketChannelImpl::Send(const CString& message) {
NETWORK_DVLOG(1) << this << " Send(" << message << ") (CString argument)";
// FIXME: Change the inspector API to show the entire message instead
// of individual frames.
probe::didSendWebSocketFrame(GetExecutionContext(), identifier_,
WebSocketOpCode::kOpCodeText, true,
message.data(), message.length());
messages_.push_back(new Message(message));
ProcessSendQueue();
}
void WebSocketChannelImpl::Send(
scoped_refptr<BlobDataHandle> blob_data_handle) {
NETWORK_DVLOG(1) << this << " Send(" << blob_data_handle->Uuid() << ", "
<< blob_data_handle->GetType() << ", "
<< blob_data_handle->size() << ") "
<< "(BlobDataHandle argument)";
// FIXME: Change the inspector API to show the entire message instead
// of individual frames.
// FIXME: We can't access the data here.
// Since Binary data are not displayed in Inspector, this does not
// affect actual behavior.
probe::didSendWebSocketFrame(GetExecutionContext(), identifier_,
WebSocketOpCode::kOpCodeBinary, true, "", 0);
messages_.push_back(new Message(std::move(blob_data_handle)));
ProcessSendQueue();
}
void WebSocketChannelImpl::Send(const DOMArrayBuffer& buffer,
unsigned byte_offset,
unsigned byte_length) {
NETWORK_DVLOG(1) << this << " Send(" << buffer.Data() << ", " << byte_offset
<< ", " << byte_length << ") "
<< "(DOMArrayBuffer argument)";
// FIXME: Change the inspector API to show the entire message instead
// of individual frames.
probe::didSendWebSocketFrame(
GetExecutionContext(), identifier_, WebSocketOpCode::kOpCodeBinary, true,
static_cast<const char*>(buffer.Data()) + byte_offset, byte_length);
// buffer.slice copies its contents.
// FIXME: Reduce copy by sending the data immediately when we don't need to
// queue the data.
messages_.push_back(
new Message(buffer.Slice(byte_offset, byte_offset + byte_length)));
ProcessSendQueue();
}
void WebSocketChannelImpl::SendTextAsCharVector(
std::unique_ptr<Vector<char>> data) {
NETWORK_DVLOG(1) << this << " SendTextAsCharVector("
<< static_cast<void*>(data.get()) << ", " << data->size()
<< ")";
// FIXME: Change the inspector API to show the entire message instead
// of individual frames.
probe::didSendWebSocketFrame(GetExecutionContext(), identifier_,
WebSocketOpCode::kOpCodeText, true, data->data(),
data->size());
messages_.push_back(
new Message(std::move(data), kMessageTypeTextAsCharVector));
ProcessSendQueue();
}
void WebSocketChannelImpl::SendBinaryAsCharVector(
std::unique_ptr<Vector<char>> data) {
NETWORK_DVLOG(1) << this << " SendBinaryAsCharVector("
<< static_cast<void*>(data.get()) << ", " << data->size()
<< ")";
// FIXME: Change the inspector API to show the entire message instead
// of individual frames.
probe::didSendWebSocketFrame(GetExecutionContext(), identifier_,
WebSocketOpCode::kOpCodeBinary, true,
data->data(), data->size());
messages_.push_back(
new Message(std::move(data), kMessageTypeBinaryAsCharVector));
ProcessSendQueue();
}
void WebSocketChannelImpl::Close(int code, const String& reason) {
NETWORK_DVLOG(1) << this << " Close(" << code << ", " << reason << ")";
DCHECK(handle_);
unsigned short code_to_send = static_cast<unsigned short>(
code == kCloseEventCodeNotSpecified ? kCloseEventCodeNoStatusRcvd : code);
messages_.push_back(new Message(code_to_send, reason));
ProcessSendQueue();
}
void WebSocketChannelImpl::Fail(const String& reason,
MessageLevel level,
std::unique_ptr<SourceLocation> location) {
NETWORK_DVLOG(1) << this << " Fail(" << reason << ")";
probe::didReceiveWebSocketFrameError(GetExecutionContext(), identifier_,
reason);
const String message =
"WebSocket connection to '" + url_.ElidedString() + "' failed: " + reason;
std::unique_ptr<SourceLocation> captured_location = SourceLocation::Capture();
if (!captured_location->IsUnknown()) {
// If we are in JavaScript context, use the current location instead
// of passed one - it's more precise.
location = std::move(captured_location);
} else if (location->IsUnknown()) {
// No information is specified by the caller. Use the line number at the
// connection.
location = location_at_construction_->Clone();
}
GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, level, message, std::move(location)));
// |reason| is only for logging and should not be provided for scripts,
// hence close reason must be empty in tearDownFailedConnection.
TearDownFailedConnection();
}
void WebSocketChannelImpl::Disconnect() {
NETWORK_DVLOG(1) << this << " disconnect()";
if (identifier_) {
TRACE_EVENT_INSTANT1(
"devtools.timeline", "WebSocketDestroy", TRACE_EVENT_SCOPE_THREAD,
"data",
InspectorWebSocketEvent::Data(GetExecutionContext(), identifier_));
probe::didCloseWebSocket(GetExecutionContext(), identifier_);
}
connection_handle_for_scheduler_.reset();
AbortAsyncOperations();
handshake_throttle_.reset();
handle_.reset();
client_ = nullptr;
identifier_ = 0;
}
WebSocketChannelImpl::Message::Message(const CString& text)
: type(kMessageTypeText), text(text) {}
WebSocketChannelImpl::Message::Message(
scoped_refptr<BlobDataHandle> blob_data_handle)
: type(kMessageTypeBlob), blob_data_handle(std::move(blob_data_handle)) {}
WebSocketChannelImpl::Message::Message(DOMArrayBuffer* array_buffer)
: type(kMessageTypeArrayBuffer), array_buffer(array_buffer) {}
WebSocketChannelImpl::Message::Message(
std::unique_ptr<Vector<char>> vector_data,
MessageType type)
: type(type), vector_data(std::move(vector_data)) {
DCHECK(type == kMessageTypeTextAsCharVector ||
type == kMessageTypeBinaryAsCharVector);
}
WebSocketChannelImpl::Message::Message(unsigned short code,
const String& reason)
: type(kMessageTypeClose), code(code), reason(reason) {}
void WebSocketChannelImpl::SendInternal(
WebSocketHandle::MessageType message_type,
const char* data,
size_t total_size,
uint64_t* consumed_buffered_amount) {
WebSocketHandle::MessageType frame_type =
sent_size_of_top_message_ ? WebSocketHandle::kMessageTypeContinuation
: message_type;
DCHECK_GE(total_size, sent_size_of_top_message_);
// The first cast is safe since the result of min() never exceeds
// the range of size_t. The second cast is necessary to compile
// min() on ILP32.
size_t size = static_cast<size_t>(
std::min(sending_quota_,
static_cast<uint64_t>(total_size - sent_size_of_top_message_)));
bool final = (sent_size_of_top_message_ + size == total_size);
handle_->Send(final, frame_type, data + sent_size_of_top_message_, size);
sent_size_of_top_message_ += size;
sending_quota_ -= size;
*consumed_buffered_amount += size;
if (final) {
messages_.pop_front();
sent_size_of_top_message_ = 0;
}
}
void WebSocketChannelImpl::ProcessSendQueue() {
DCHECK(handle_);
uint64_t consumed_buffered_amount = 0;
while (!messages_.IsEmpty() && !blob_loader_) {
Message* message = messages_.front().Get();
CHECK(message);
if (sending_quota_ == 0 && message->type != kMessageTypeClose)
break;
switch (message->type) {
case kMessageTypeText:
SendInternal(WebSocketHandle::kMessageTypeText, message->text.data(),
message->text.length(), &consumed_buffered_amount);
break;
case kMessageTypeBlob:
CHECK(!blob_loader_);
CHECK(message);
CHECK(message->blob_data_handle);
blob_loader_ = new BlobLoader(message->blob_data_handle, this);
break;
case kMessageTypeArrayBuffer:
CHECK(message->array_buffer);
SendInternal(WebSocketHandle::kMessageTypeBinary,
static_cast<const char*>(message->array_buffer->Data()),
message->array_buffer->ByteLength(),
&consumed_buffered_amount);
break;
case kMessageTypeTextAsCharVector:
CHECK(message->vector_data);
SendInternal(WebSocketHandle::kMessageTypeText,
message->vector_data->data(), message->vector_data->size(),
&consumed_buffered_amount);
break;
case kMessageTypeBinaryAsCharVector:
CHECK(message->vector_data);
SendInternal(WebSocketHandle::kMessageTypeBinary,
message->vector_data->data(), message->vector_data->size(),
&consumed_buffered_amount);
break;
case kMessageTypeClose: {
// No message should be sent from now on.
DCHECK_EQ(messages_.size(), 1u);
DCHECK_EQ(sent_size_of_top_message_, 0u);
handshake_throttle_.reset();
handle_->Close(message->code, message->reason);
messages_.pop_front();
break;
}
}
}
if (client_ && consumed_buffered_amount > 0)
client_->DidConsumeBufferedAmount(consumed_buffered_amount);
}
void WebSocketChannelImpl::FlowControlIfNecessary() {
if (!handle_ || received_data_size_for_flow_control_ <
kReceivedDataSizeForFlowControlHighWaterMark) {
return;
}
handle_->FlowControl(received_data_size_for_flow_control_);
received_data_size_for_flow_control_ = 0;
}
void WebSocketChannelImpl::InitialFlowControl() {
DCHECK_EQ(received_data_size_for_flow_control_, 0u);
DCHECK(handle_);
handle_->FlowControl(kReceivedDataSizeForFlowControlHighWaterMark * 2);
}
void WebSocketChannelImpl::AbortAsyncOperations() {
if (blob_loader_) {
blob_loader_->Cancel();
blob_loader_.Clear();
}
}
void WebSocketChannelImpl::HandleDidClose(bool was_clean,
unsigned short code,
const String& reason) {
handshake_throttle_.reset();
handle_.reset();
AbortAsyncOperations();
if (!client_) {
return;
}
WebSocketChannelClient* client = client_;
client_ = nullptr;
WebSocketChannelClient::ClosingHandshakeCompletionStatus status =
was_clean ? WebSocketChannelClient::kClosingHandshakeComplete
: WebSocketChannelClient::kClosingHandshakeIncomplete;
client->DidClose(status, code, reason);
// client->DidClose may delete this object.
}
ExecutionContext* WebSocketChannelImpl::GetExecutionContext() const {
return loading_context_->GetExecutionContext();
}
void WebSocketChannelImpl::DidConnect(WebSocketHandle* handle,
const String& selected_protocol,
const String& extensions) {
NETWORK_DVLOG(1) << this << " DidConnect(" << handle << ", "
<< String(selected_protocol) << ", " << String(extensions)
<< ")";
DCHECK(handle_);
DCHECK_EQ(handle, handle_.get());
DCHECK(client_);
if (!throttle_passed_) {
connect_info_ =
std::make_unique<ConnectInfo>(selected_protocol, extensions);
return;
}
InitialFlowControl();
handshake_throttle_.reset();
client_->DidConnect(selected_protocol, extensions);
}
void WebSocketChannelImpl::DidStartOpeningHandshake(
WebSocketHandle* handle,
scoped_refptr<WebSocketHandshakeRequest> request) {
NETWORK_DVLOG(1) << this << " DidStartOpeningHandshake(" << handle << ")";
DCHECK(handle_);
DCHECK_EQ(handle, handle_.get());
TRACE_EVENT_INSTANT1(
"devtools.timeline", "WebSocketSendHandshakeRequest",
TRACE_EVENT_SCOPE_THREAD, "data",
InspectorWebSocketEvent::Data(GetExecutionContext(), identifier_));
probe::willSendWebSocketHandshakeRequest(GetExecutionContext(), identifier_,
request.get());
handshake_request_ = std::move(request);
}
void WebSocketChannelImpl::DidFinishOpeningHandshake(
WebSocketHandle* handle,
const WebSocketHandshakeResponse* response) {
NETWORK_DVLOG(1) << this << " DidFinishOpeningHandshake(" << handle << ")";
DCHECK(handle_);
DCHECK_EQ(handle, handle_.get());
TRACE_EVENT_INSTANT1(
"devtools.timeline", "WebSocketReceiveHandshakeResponse",
TRACE_EVENT_SCOPE_THREAD, "data",
InspectorWebSocketEvent::Data(GetExecutionContext(), identifier_));
probe::didReceiveWebSocketHandshakeResponse(
GetExecutionContext(), identifier_, handshake_request_.get(), response);
handshake_request_ = nullptr;
}
void WebSocketChannelImpl::DidFail(WebSocketHandle* handle,
const String& message) {
NETWORK_DVLOG(1) << this << " DidFail(" << handle << ", " << String(message)
<< ")";
connection_handle_for_scheduler_.reset();
DCHECK(handle_);
DCHECK_EQ(handle, handle_.get());
// This function is called when the browser is required to fail the
// WebSocketConnection. Hence we fail this channel by calling
// |this->failAsError| function.
FailAsError(message);
// |this| may be deleted.
}
void WebSocketChannelImpl::DidReceiveData(WebSocketHandle* handle,
bool fin,
WebSocketHandle::MessageType type,
const char* data,
size_t size) {
NETWORK_DVLOG(1) << this << " DidReceiveData(" << handle << ", " << fin
<< ", " << type << ", (" << static_cast<const void*>(data)
<< ", " << size << "))";
DCHECK(handle_);
DCHECK_EQ(handle, handle_.get());
DCHECK(client_);
// Non-final frames cannot be empty.
DCHECK(fin || size);
switch (type) {
case WebSocketHandle::kMessageTypeText:
DCHECK(receiving_message_data_.IsEmpty());
receiving_message_type_is_text_ = true;
break;
case WebSocketHandle::kMessageTypeBinary:
DCHECK(receiving_message_data_.IsEmpty());
receiving_message_type_is_text_ = false;
break;
case WebSocketHandle::kMessageTypeContinuation:
DCHECK(!receiving_message_data_.IsEmpty());
break;
}
receiving_message_data_.Append(data, size);
received_data_size_for_flow_control_ += size;
FlowControlIfNecessary();
if (!fin) {
return;
}
// FIXME: Change the inspector API to show the entire message instead
// of individual frames.
auto opcode = receiving_message_type_is_text_
? WebSocketOpCode::kOpCodeText
: WebSocketOpCode::kOpCodeBinary;
probe::didReceiveWebSocketFrame(GetExecutionContext(), identifier_, opcode,
false, receiving_message_data_.data(),
receiving_message_data_.size());
if (receiving_message_type_is_text_) {
String message = receiving_message_data_.IsEmpty()
? g_empty_string
: String::FromUTF8(receiving_message_data_.data(),
receiving_message_data_.size());
receiving_message_data_.clear();
if (message.IsNull()) {
FailAsError("Could not decode a text frame as UTF-8.");
// failAsError may delete this object.
} else {
client_->DidReceiveTextMessage(message);
}
} else {
std::unique_ptr<Vector<char>> binary_data =
std::make_unique<Vector<char>>();
binary_data->swap(receiving_message_data_);
client_->DidReceiveBinaryMessage(std::move(binary_data));
}
}
void WebSocketChannelImpl::DidClose(WebSocketHandle* handle,
bool was_clean,
unsigned short code,
const String& reason) {
NETWORK_DVLOG(1) << this << " DidClose(" << handle << ", " << was_clean
<< ", " << code << ", " << String(reason) << ")";
connection_handle_for_scheduler_.reset();
DCHECK(handle_);
DCHECK_EQ(handle, handle_.get());
handle_.reset();
if (identifier_) {
TRACE_EVENT_INSTANT1(
"devtools.timeline", "WebSocketDestroy", TRACE_EVENT_SCOPE_THREAD,
"data",
InspectorWebSocketEvent::Data(GetExecutionContext(), identifier_));
probe::didCloseWebSocket(GetExecutionContext(), identifier_);
identifier_ = 0;
}
HandleDidClose(was_clean, code, reason);
// HandleDidClose may delete this object.
}
void WebSocketChannelImpl::DidReceiveFlowControl(WebSocketHandle* handle,
int64_t quota) {
NETWORK_DVLOG(1) << this << " DidReceiveFlowControl(" << handle << ", "
<< quota << ")";
DCHECK(handle_);
DCHECK_EQ(handle, handle_.get());
DCHECK_GE(quota, 0);
sending_quota_ += quota;
ProcessSendQueue();
}
void WebSocketChannelImpl::DidStartClosingHandshake(WebSocketHandle* handle) {
NETWORK_DVLOG(1) << this << " DidStartClosingHandshake(" << handle << ")";
DCHECK(handle_);
DCHECK_EQ(handle, handle_.get());
if (client_)
client_->DidStartClosingHandshake();
}
void WebSocketChannelImpl::OnSuccess() {
DCHECK(!throttle_passed_);
DCHECK(handshake_throttle_);
throttle_passed_ = true;
handshake_throttle_ = nullptr;
if (connect_info_) {
// No flow control quota is supplied to the browser until we are ready to
// receive messages. This fixes crbug.com/786776.
InitialFlowControl();
client_->DidConnect(std::move(connect_info_->selected_protocol),
std::move(connect_info_->extensions));
connect_info_.reset();
}
}
void WebSocketChannelImpl::OnError(const WebString& console_message) {
DCHECK(!throttle_passed_);
DCHECK(handshake_throttle_);
handshake_throttle_ = nullptr;
FailAsError(console_message);
}
void WebSocketChannelImpl::DidFinishLoadingBlob(DOMArrayBuffer* buffer) {
blob_loader_.Clear();
DCHECK(handle_);
// The loaded blob is always placed on |messages_[0]|.
DCHECK_GT(messages_.size(), 0u);
DCHECK_EQ(messages_.front()->type, kMessageTypeBlob);
// We replace it with the loaded blob.
messages_.front() = new Message(buffer);
ProcessSendQueue();
}
void WebSocketChannelImpl::DidFailLoadingBlob(FileError::ErrorCode error_code) {
blob_loader_.Clear();
if (error_code == FileError::kAbortErr) {
// The error is caused by cancel().
return;
}
// FIXME: Generate human-friendly reason message.
FailAsError("Failed to load Blob: error code = " +
String::Number(error_code));
// |this| can be deleted here.
}
void WebSocketChannelImpl::TearDownFailedConnection() {
// |handle_| and |client_| can be null here.
connection_handle_for_scheduler_.reset();
handshake_throttle_.reset();
if (client_)
client_->DidError();
HandleDidClose(false, kCloseEventCodeAbnormalClosure, String());
// HandleDidClose may delete this object.
}
bool WebSocketChannelImpl::ShouldDisallowConnection(const KURL& url) {
DCHECK(handle_);
BaseFetchContext* fetch_context = loading_context_->GetFetchContext();
SubresourceFilter* subresource_filter = fetch_context->GetSubresourceFilter();
if (!subresource_filter)
return false;
return !subresource_filter->AllowWebSocketConnection(url);
}
void WebSocketChannelImpl::Trace(blink::Visitor* visitor) {
visitor->Trace(blob_loader_);
visitor->Trace(messages_);
visitor->Trace(client_);
visitor->Trace(loading_context_);
WebSocketChannel::Trace(visitor);
}
std::ostream& operator<<(std::ostream& ostream,
const WebSocketChannelImpl* channel) {
return ostream << "WebSocketChannelImpl "
<< static_cast<const void*>(channel);
}
} // namespace blink