blob: c33cdbc26532f8f3ec39fbaf815e86fdec7f4803 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/websockets/websocket_stream.h"
#include <memory>
#include <string>
#include <utility>
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_array_buffer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_websocket_close_info.h"
#include "third_party/blink/renderer/core/dom/abort_signal.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/core/streams/readable_stream_default_controller_interface.h"
#include "third_party/blink/renderer/core/streams/underlying_sink_base.h"
#include "third_party/blink/renderer/core/streams/underlying_source_base.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/core/streams/writable_stream_default_controller_interface.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
#include "third_party/blink/renderer/modules/websockets/websocket_channel_impl.h"
#include "third_party/blink/renderer/modules/websockets/websocket_close_info.h"
#include "third_party/blink/renderer/modules/websockets/websocket_connection.h"
#include "third_party/blink/renderer/modules/websockets/websocket_stream_options.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
#include "third_party/blink/renderer/platform/loader/mixed_content_autoupgrade_status.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
namespace blink {
class WebSocketStream::UnderlyingSource final : public UnderlyingSourceBase {
public:
UnderlyingSource(ScriptState* script_state, WebSocketStream* creator)
: UnderlyingSourceBase(script_state), creator_(creator) {}
// UnderlyingSourceBase implementation.
ScriptPromise pull(ScriptState*) override;
ScriptPromise Cancel(ScriptState*, ScriptValue reason) override;
// API for WebSocketStream.
void DidReceiveTextMessage(const String&);
void DidReceiveBinaryMessage(const Vector<base::span<const char>>&);
void DidStartClosingHandshake();
void DidClose(bool was_clean, uint16_t code, const String& reason);
void Trace(Visitor* visitor) override {
visitor->Trace(creator_);
UnderlyingSourceBase::Trace(visitor);
}
private:
Member<WebSocketStream> creator_;
bool closed_ = false;
};
class WebSocketStream::UnderlyingSink final : public UnderlyingSinkBase {
public:
explicit UnderlyingSink(WebSocketStream* creator) : creator_(creator) {}
// UnderlyingSinkBase implementation.
ScriptPromise start(ScriptState*,
WritableStreamDefaultControllerInterface*) override;
ScriptPromise write(ScriptState*,
ScriptValue chunk,
WritableStreamDefaultControllerInterface*) override;
ScriptPromise close(ScriptState*) override;
ScriptPromise abort(ScriptState*, ScriptValue reason) override;
// API for WebSocketStream.
void DidStartClosingHandshake();
void DidClose(bool was_clean, uint16_t code, const String& reason);
bool AllDataHasBeenConsumed() { return !is_writing_; }
void Trace(Visitor* visitor) override {
visitor->Trace(creator_);
visitor->Trace(close_resolver_);
UnderlyingSinkBase::Trace(visitor);
}
private:
void ErrorControllerBecauseClosed();
void FinishWriteCallback(ScriptPromiseResolver*);
void ResolveClose(bool was_clean);
void SendAny(ScriptState*,
v8::Local<v8::Value> v8chunk,
ScriptPromiseResolver*,
base::OnceClosure callback);
void SendArrayBuffer(ScriptState*,
DOMArrayBuffer*,
int offset,
int length,
ScriptPromiseResolver*,
base::OnceClosure callback);
void SendString(ScriptState*,
v8::Local<v8::Value> v8chunk,
ScriptPromiseResolver*,
base::OnceClosure callback);
Member<WebSocketStream> creator_;
Member<ScriptPromiseResolver> close_resolver_;
bool closed_ = false;
bool is_writing_ = false;
};
ScriptPromise WebSocketStream::UnderlyingSource::pull(
ScriptState* script_state) {
DVLOG(1) << "WebSocketStream::UnderlyingSource " << this << " pull()";
creator_->channel_->RemoveBackpressure();
return ScriptPromise::CastUndefined(script_state);
}
ScriptPromise WebSocketStream::UnderlyingSource::Cancel(
ScriptState* script_state,
ScriptValue reason) {
DVLOG(1) << "WebSocketStream::UnderlyingSource " << this << " Cancel()";
closed_ = true;
creator_->CloseMaybeWithReason(reason);
return ScriptPromise::CastUndefined(script_state);
}
void WebSocketStream::UnderlyingSource::DidReceiveTextMessage(
const String& string) {
DVLOG(1) << "WebSocketStream::UnderlyingSource " << this
<< " DidReceiveTextMessage() string=" << string;
DCHECK(!closed_);
Controller()->Enqueue(string);
creator_->channel_->ApplyBackpressure();
}
void WebSocketStream::UnderlyingSource::DidReceiveBinaryMessage(
const Vector<base::span<const char>>& data) {
DVLOG(1) << "WebSocketStream::UnderlyingSource " << this
<< " DidReceiveBinaryMessage()";
DCHECK(!closed_);
auto* buffer = DOMArrayBuffer::Create(data);
Controller()->Enqueue(buffer);
creator_->channel_->ApplyBackpressure();
}
void WebSocketStream::UnderlyingSource::DidStartClosingHandshake() {
DVLOG(1) << "WebSocketStream::UnderlyingSource " << this
<< " DidStartClosingHandshake()";
DCHECK(!closed_);
Controller()->Close();
closed_ = true;
}
void WebSocketStream::UnderlyingSource::DidClose(bool was_clean,
uint16_t code,
const String& reason) {
DVLOG(1) << "WebSocketStream::UnderlyingSource " << this
<< " DidClose() was_clean=" << was_clean << " code=" << code
<< " reason=" << reason;
if (closed_)
return;
closed_ = true;
if (was_clean) {
Controller()->Close();
return;
}
Controller()->Error(creator_->CreateNetworkErrorDOMException());
}
ScriptPromise WebSocketStream::UnderlyingSink::start(
ScriptState* script_state,
WritableStreamDefaultControllerInterface*) {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this << " start()";
return ScriptPromise::CastUndefined(script_state);
}
ScriptPromise WebSocketStream::UnderlyingSink::write(
ScriptState* script_state,
ScriptValue chunk,
WritableStreamDefaultControllerInterface*) {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this << " write()";
is_writing_ = true;
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise result = resolver->Promise();
base::OnceClosure callback =
WTF::Bind(&UnderlyingSink::FinishWriteCallback, WrapWeakPersistent(this),
WrapPersistent(resolver));
v8::Local<v8::Value> v8chunk = chunk.V8Value();
SendAny(script_state, v8chunk, resolver, std::move(callback));
return result;
}
ScriptPromise WebSocketStream::UnderlyingSink::close(
ScriptState* script_state) {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this << " close()";
closed_ = true;
creator_->CloseWithUnspecifiedCode();
DCHECK(!close_resolver_);
close_resolver_ = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
return close_resolver_->Promise();
}
ScriptPromise WebSocketStream::UnderlyingSink::abort(ScriptState* script_state,
ScriptValue reason) {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this << " abort()";
closed_ = true;
creator_->CloseMaybeWithReason(reason);
return ScriptPromise::CastUndefined(script_state);
}
void WebSocketStream::UnderlyingSink::DidStartClosingHandshake() {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this
<< " DidStartClosingHandshake()";
if (closed_)
return;
closed_ = true;
ErrorControllerBecauseClosed();
}
void WebSocketStream::UnderlyingSink::DidClose(bool was_clean,
uint16_t code,
const String& reason) {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this
<< " DidClose() was_clean=" << was_clean << " code=" << code
<< " reason=" << reason;
if (close_resolver_)
ResolveClose(was_clean);
if (closed_)
return;
closed_ = true;
if (was_clean) {
ErrorControllerBecauseClosed();
return;
}
Controller()->Error(creator_->script_state_,
creator_->CreateNetworkErrorDOMException());
}
void WebSocketStream::UnderlyingSink::ErrorControllerBecauseClosed() {
ScriptState* script_state = creator_->script_state_;
Controller()->Error(
script_state,
V8ThrowDOMException::CreateOrEmpty(
script_state->GetIsolate(), DOMExceptionCode::kInvalidStateError,
"Cannot write to a closed WebSocketStream"));
}
void WebSocketStream::UnderlyingSink::FinishWriteCallback(
ScriptPromiseResolver* resolver) {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this
<< " FinishWriteCallback()";
resolver->Resolve();
is_writing_ = false;
}
void WebSocketStream::UnderlyingSink::ResolveClose(bool was_clean) {
DCHECK(close_resolver_);
if (was_clean) {
close_resolver_->Resolve();
return;
}
close_resolver_->Reject(creator_->CreateNetworkErrorDOMException());
}
void WebSocketStream::UnderlyingSink::SendAny(ScriptState* script_state,
v8::Local<v8::Value> v8chunk,
ScriptPromiseResolver* resolver,
base::OnceClosure callback) {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this << " SendAny()";
auto* isolate = script_state->GetIsolate();
if (v8chunk->IsArrayBuffer()) {
DOMArrayBuffer* data = V8ArrayBuffer::ToImpl(v8chunk.As<v8::ArrayBuffer>());
SendArrayBuffer(script_state, data, 0, data->ByteLength(), resolver,
std::move(callback));
return;
}
if (v8chunk->IsArrayBufferView()) {
ExceptionState exception_state(isolate, ExceptionState::kUnknownContext, "",
"");
NotShared<DOMArrayBufferView> data =
ToNotShared<NotShared<DOMArrayBufferView>>(isolate, v8chunk,
exception_state);
if (exception_state.HadException()) {
closed_ = true;
is_writing_ = false;
resolver->Reject(exception_state);
return;
}
SendArrayBuffer(script_state, data.View()->buffer(),
data.View()->byteOffset(), data.View()->byteLength(),
resolver, std::move(callback));
return;
}
SendString(script_state, v8chunk, resolver, std::move(callback));
}
void WebSocketStream::UnderlyingSink::SendArrayBuffer(
ScriptState* script_state,
DOMArrayBuffer* buffer,
int offset,
int length,
ScriptPromiseResolver* resolver,
base::OnceClosure callback) {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this
<< " SendArrayBuffer() buffer = " << buffer << " offset = " << offset
<< " length = " << length;
if (creator_->channel_->Send(*buffer, offset, length, std::move(callback)) ==
WebSocketChannel::SendResult::SENT_SYNCHRONOUSLY) {
is_writing_ = false;
resolver->Resolve();
}
}
void WebSocketStream::UnderlyingSink::SendString(
ScriptState* script_state,
v8::Local<v8::Value> v8chunk,
ScriptPromiseResolver* resolver,
base::OnceClosure callback) {
DVLOG(1) << "WebSocketStream::UnderlyingSink " << this << " SendString()";
auto* isolate = script_state->GetIsolate();
v8::TryCatch try_catch(isolate);
v8::Local<v8::String> string_chunk;
if (!v8chunk->ToString(script_state->GetContext()).ToLocal(&string_chunk)) {
closed_ = true;
resolver->Reject(try_catch.Exception());
is_writing_ = false;
return;
}
// Skip one string copy by using v8::String UTF8 conversion instead of going
// via WTF::String.
int expected_length = string_chunk->Utf8Length(isolate) + 1;
std::string message(expected_length, '\0');
int written_length = string_chunk->WriteUtf8(
isolate, &message[0], -1, nullptr, v8::String::REPLACE_INVALID_UTF8);
DCHECK_EQ(expected_length, written_length);
DCHECK_GT(expected_length, 0);
DCHECK_EQ(message.back(), '\0');
message.pop_back(); // Remove the null terminator.
if (creator_->channel_->Send(message, std::move(callback)) ==
WebSocketChannel::SendResult::SENT_SYNCHRONOUSLY) {
is_writing_ = false;
resolver->Resolve();
}
}
WebSocketStream* WebSocketStream::Create(ScriptState* script_state,
const String& url,
WebSocketStreamOptions* options,
ExceptionState& exception_state) {
DVLOG(1) << "WebSocketStream Create() url=" << url << " options=" << options;
return CreateInternal(script_state, url, options, nullptr, exception_state);
}
WebSocketStream* WebSocketStream::CreateForTesting(
ScriptState* script_state,
const String& url,
WebSocketStreamOptions* options,
WebSocketChannel* channel,
ExceptionState& exception_state) {
DVLOG(1) << "WebSocketStream CreateForTesting() url=" << url
<< " options=" << options << " channel=" << channel;
DCHECK(channel) << "Don't use a real channel when testing";
return CreateInternal(script_state, url, options, channel, exception_state);
}
WebSocketStream* WebSocketStream::CreateInternal(
ScriptState* script_state,
const String& url,
WebSocketStreamOptions* options,
WebSocketChannel* channel,
ExceptionState& exception_state) {
DVLOG(1) << "WebSocketStream CreateInternal() url=" << url
<< " options=" << options << " channel=" << channel;
auto* execution_context = ExecutionContext::From(script_state);
auto* stream =
MakeGarbageCollected<WebSocketStream>(execution_context, script_state);
if (channel) {
stream->channel_ = channel;
} else {
stream->channel_ = WebSocketChannelImpl::Create(
execution_context, stream, SourceLocation::Capture(execution_context));
}
stream->Connect(script_state, url, options, exception_state);
if (exception_state.HadException())
return nullptr;
return stream;
}
WebSocketStream::WebSocketStream(ExecutionContext* execution_context,
ScriptState* script_state)
: ContextLifecycleObserver(execution_context),
script_state_(script_state),
connection_resolver_(
MakeGarbageCollected<ScriptPromiseResolver>(script_state)),
closed_resolver_(
MakeGarbageCollected<ScriptPromiseResolver>(script_state)),
connection_(script_state->GetIsolate(),
connection_resolver_->Promise().V8Value().As<v8::Promise>()),
closed_(script_state->GetIsolate(),
closed_resolver_->Promise().V8Value().As<v8::Promise>()) {}
WebSocketStream::~WebSocketStream() = default;
ScriptPromise WebSocketStream::connection(ScriptState* script_state) const {
return ScriptPromise(script_state,
connection_.NewLocal(script_state->GetIsolate()));
}
ScriptPromise WebSocketStream::closed(ScriptState* script_state) const {
return ScriptPromise(script_state,
closed_.NewLocal(script_state->GetIsolate()));
}
void WebSocketStream::close(WebSocketCloseInfo* info,
ExceptionState& exception_state) {
DVLOG(1) << "WebSocketStream " << this << " close() info=" << info;
int code = WebSocketChannel::kCloseEventCodeNotSpecified;
String reason = info->reason();
if (info->hasCode()) {
code = info->code();
} else if (!reason.IsNull() && !reason.IsEmpty()) {
code = WebSocketChannel::kCloseEventCodeNormalClosure;
}
CloseInternal(code, info->reason(), exception_state);
}
void WebSocketStream::DidConnect(const String& subprotocol,
const String& extensions) {
DVLOG(1) << "WebSocketStream " << this
<< " DidConnect() subprotocol=" << subprotocol
<< " extensions=" << extensions;
if (!channel_)
return;
ScriptState::Scope scope(script_state_);
common_.LogMixedAutoupgradeStatus(
MixedContentAutoupgradeStatus::kResponseReceived);
if (common_.GetState() != WebSocketCommon::kConnecting)
return;
common_.SetState(WebSocketCommon::kOpen);
was_ever_connected_ = true;
auto* connection = MakeGarbageCollected<WebSocketConnection>();
connection->setProtocol(subprotocol);
connection->setExtensions(extensions);
source_ = MakeGarbageCollected<UnderlyingSource>(script_state_, this);
auto* readable = ReadableStream::CreateWithCountQueueingStrategy(
script_state_, source_, 0);
sink_ = MakeGarbageCollected<UnderlyingSink>(this);
auto* writable =
WritableStream::CreateWithCountQueueingStrategy(script_state_, sink_, 1);
connection->setReadable(readable);
connection->setWritable(writable);
connection_resolver_->Resolve(connection);
}
void WebSocketStream::DidReceiveTextMessage(const String& string) {
DVLOG(1) << "WebSocketStream " << this
<< " DidReceiveTextMessage() string=" << string;
if (!channel_)
return;
ScriptState::Scope scope(script_state_);
source_->DidReceiveTextMessage(string);
}
void WebSocketStream::DidReceiveBinaryMessage(
const Vector<base::span<const char>>& data) {
DVLOG(1) << "WebSocketStream " << this << " DidReceiveBinaryMessage()";
if (!channel_)
return;
ScriptState::Scope scope(script_state_);
source_->DidReceiveBinaryMessage(data);
}
void WebSocketStream::DidError() {
// This is not useful as it is always followed by a call to DidClose().
}
void WebSocketStream::DidConsumeBufferedAmount(uint64_t consumed) {
// This is only relevant to DOMWebSocket.
}
void WebSocketStream::DidStartClosingHandshake() {
DVLOG(1) << "WebSocketStream " << this << " DidStartClosingHandshake()";
if (!channel_)
return;
ScriptState::Scope scope(script_state_);
common_.SetState(WebSocketCommon::kClosing);
source_->DidStartClosingHandshake();
sink_->DidStartClosingHandshake();
}
void WebSocketStream::DidClose(
ClosingHandshakeCompletionStatus closing_handshake_completion,
uint16_t code,
const String& reason) {
DVLOG(1) << "WebSocketStream " << this
<< " DidClose() closing_handshake_completion="
<< closing_handshake_completion << " code=" << code
<< " reason=" << reason;
if (!channel_)
return;
ScriptState::Scope scope(script_state_);
if (!was_ever_connected_) {
connection_resolver_->Reject(CreateNetworkErrorDOMException());
common_.LogMixedAutoupgradeStatus(MixedContentAutoupgradeStatus::kFailed);
}
bool all_data_was_consumed = sink_ ? sink_->AllDataHasBeenConsumed() : true;
bool was_clean = common_.GetState() == WebSocketCommon::kClosing &&
all_data_was_consumed &&
closing_handshake_completion == kClosingHandshakeComplete &&
code != WebSocketChannel::kCloseEventCodeAbnormalClosure;
common_.SetState(WebSocketCommon::kClosed);
channel_->Disconnect();
channel_ = nullptr;
if (source_)
source_->DidClose(was_clean, code, reason);
if (sink_)
sink_->DidClose(was_clean, code, reason);
if (was_clean) {
closed_resolver_->Resolve(MakeCloseInfo(code, reason));
} else {
closed_resolver_->Reject(CreateNetworkErrorDOMException());
}
}
void WebSocketStream::ContextDestroyed(ExecutionContext*) {
DVLOG(1) << "WebSocketStream " << this << " ContextDestroyed()";
if (channel_) {
if (common_.GetState() == WebSocketCommon::kOpen) {
channel_->Close(WebSocketChannel::kCloseEventCodeGoingAway, String());
}
channel_ = nullptr;
}
if (common_.GetState() != WebSocketCommon::kClosed) {
common_.SetState(WebSocketCommon::kClosed);
}
}
bool WebSocketStream::HasPendingActivity() const {
return channel_;
}
void WebSocketStream::Trace(Visitor* visitor) {
visitor->Trace(script_state_);
visitor->Trace(connection_resolver_);
visitor->Trace(closed_resolver_);
visitor->Trace(connection_);
visitor->Trace(closed_);
visitor->Trace(channel_);
visitor->Trace(source_);
visitor->Trace(sink_);
ScriptWrappable::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
WebSocketChannelClient::Trace(visitor);
}
void WebSocketStream::Connect(ScriptState* script_state,
const String& url,
WebSocketStreamOptions* options,
ExceptionState& exception_state) {
DVLOG(1) << "WebSocketStream " << this << " Connect() url=" << url
<< " options=" << options;
// Don't read all of a huge initial message before read() has been called.
channel_->ApplyBackpressure();
auto* signal = options->signal();
if (signal && signal->aborted()) {
auto exception = V8ThrowDOMException::CreateOrEmpty(
script_state->GetIsolate(), DOMExceptionCode::kAbortError,
"WebSocket handshake was aborted");
connection_resolver_->Reject(exception);
closed_resolver_->Reject(exception);
return;
}
if (signal) {
signal->AddAlgorithm(
WTF::Bind(&WebSocketStream::OnAbort, WrapWeakPersistent(this)));
}
auto result = common_.Connect(
ExecutionContext::From(script_state), url,
options->hasProtocols() ? options->protocols() : Vector<String>(),
channel_, exception_state);
switch (result) {
case WebSocketCommon::ConnectResult::kSuccess:
DCHECK(!exception_state.HadException());
return;
case WebSocketCommon::ConnectResult::kException:
DCHECK(exception_state.HadException());
channel_ = nullptr;
// These need to be resolved to stop ScriptPromiseResolver::Dispose() from
// DCHECKing. It's not actually visible to JavaScript.
connection_resolver_->Resolve();
closed_resolver_->Resolve();
return;
case WebSocketCommon::ConnectResult::kAsyncError:
DCHECK(!exception_state.HadException());
auto exception = V8ThrowDOMException::CreateOrEmpty(
script_state->GetIsolate(), DOMExceptionCode::kSecurityError,
"An attempt was made to break through the security policy of the "
"user agent.",
"WebSocket mixed content check failed.");
connection_resolver_->Reject(exception);
closed_resolver_->Reject(exception);
return;
}
}
// If |maybe_reason| contains a valid code and reason, then closes with it,
// otherwise closes with unspecified code and reason.
void WebSocketStream::CloseMaybeWithReason(ScriptValue maybe_reason) {
DVLOG(1) << "WebSocketStream " << this << " CloseMaybeWithReason()";
// Exceptions thrown here are ignored.
ExceptionState exception_state(script_state_->GetIsolate(),
ExceptionState::kUnknownContext, "", "");
WebSocketCloseInfo* info = MakeGarbageCollected<WebSocketCloseInfo>();
V8WebSocketCloseInfo::ToImpl(script_state_->GetIsolate(),
maybe_reason.V8Value(), info, exception_state);
if (info->hasCode() && !exception_state.HadException()) {
CloseInternal(info->code(), info->reason(), exception_state);
if (!exception_state.HadException())
return;
}
// We couldn't convert it, but that's okay.
if (exception_state.HadException()) {
exception_state.ClearException();
}
CloseWithUnspecifiedCode();
}
void WebSocketStream::CloseWithUnspecifiedCode() {
DVLOG(1) << "WebSocketStream " << this << " CloseWithUnspecifiedCode()";
ExceptionState exception_state(script_state_->GetIsolate(),
ExceptionState::kUnknownContext, "", "");
CloseInternal(WebSocketChannel::kCloseEventCodeNotSpecified, String(),
exception_state);
DCHECK(!exception_state.HadException());
}
void WebSocketStream::CloseInternal(int code,
const String& reason,
ExceptionState& exception_state) {
DVLOG(1) << "WebSocketStream " << this << " CloseInternal() code=" << code
<< " reason=" << reason;
common_.CloseInternal(code, reason, channel_, exception_state);
}
v8::Local<v8::Value> WebSocketStream::CreateNetworkErrorDOMException() {
return V8ThrowDOMException::CreateOrEmpty(script_state_->GetIsolate(),
DOMExceptionCode::kNetworkError,
"A network error occurred");
}
void WebSocketStream::OnAbort() {
DVLOG(1) << "WebSocketStream " << this << " OnAbort()";
if (was_ever_connected_ || !channel_)
return;
channel_->CancelHandshake();
channel_ = nullptr;
auto exception = V8ThrowDOMException::CreateOrEmpty(
script_state_->GetIsolate(), DOMExceptionCode::kAbortError,
"WebSocket handshake was aborted");
connection_resolver_->Reject(exception);
closed_resolver_->Reject(exception);
}
WebSocketCloseInfo* WebSocketStream::MakeCloseInfo(uint16_t code,
const String& reason) {
DVLOG(1) << "WebSocketStream MakeCloseInfo() code=" << code
<< " reason=" << reason;
auto* info = MakeGarbageCollected<WebSocketCloseInfo>();
info->setCode(code);
info->setReason(reason);
return info;
}
} // namespace blink