blob: 0c3875d43369158302092d6dd093bf767b227c0f [file] [log] [blame]
// Copyright 2014 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/presentation/presentation_connection.h"
#include <memory>
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/events/message_event.h"
#include "third_party/blink/renderer/core/fileapi/file_error.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/deprecation.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/use_counter.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/event_target_modules.h"
#include "third_party/blink/renderer/modules/presentation/presentation.h"
#include "third_party/blink/renderer/modules/presentation/presentation_connection_available_event.h"
#include "third_party/blink/renderer/modules/presentation/presentation_connection_close_event.h"
#include "third_party/blink/renderer/modules/presentation/presentation_controller.h"
#include "third_party/blink/renderer/modules/presentation/presentation_receiver.h"
#include "third_party/blink/renderer/modules/presentation/presentation_request.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
namespace blink {
namespace {
mojom::blink::PresentationConnectionMessagePtr MakeBinaryMessage(
const DOMArrayBuffer* buffer) {
// Mutating the data field on the message instead of passing in an already
// populated Vector into message constructor is more efficient since the
// latter does not support moves.
auto message = mojom::blink::PresentationConnectionMessage::NewData(
WTF::Vector<uint8_t>());
WTF::Vector<uint8_t>& data = message->get_data();
data.Append(static_cast<const uint8_t*>(buffer->Data()),
buffer->ByteLength());
return message;
}
mojom::blink::PresentationConnectionMessagePtr MakeTextMessage(
const String& text) {
return mojom::blink::PresentationConnectionMessage::NewMessage(text);
}
const AtomicString& ConnectionStateToString(
mojom::blink::PresentationConnectionState state) {
DEFINE_STATIC_LOCAL(const AtomicString, connecting_value, ("connecting"));
DEFINE_STATIC_LOCAL(const AtomicString, connected_value, ("connected"));
DEFINE_STATIC_LOCAL(const AtomicString, closed_value, ("closed"));
DEFINE_STATIC_LOCAL(const AtomicString, terminated_value, ("terminated"));
switch (state) {
case mojom::blink::PresentationConnectionState::CONNECTING:
return connecting_value;
case mojom::blink::PresentationConnectionState::CONNECTED:
return connected_value;
case mojom::blink::PresentationConnectionState::CLOSED:
return closed_value;
case mojom::blink::PresentationConnectionState::TERMINATED:
return terminated_value;
}
NOTREACHED();
return terminated_value;
}
const AtomicString& ConnectionCloseReasonToString(
mojom::blink::PresentationConnectionCloseReason reason) {
DEFINE_STATIC_LOCAL(const AtomicString, error_value, ("error"));
DEFINE_STATIC_LOCAL(const AtomicString, closed_value, ("closed"));
DEFINE_STATIC_LOCAL(const AtomicString, went_away_value, ("wentaway"));
switch (reason) {
case mojom::blink::PresentationConnectionCloseReason::CONNECTION_ERROR:
return error_value;
case mojom::blink::PresentationConnectionCloseReason::CLOSED:
return closed_value;
case mojom::blink::PresentationConnectionCloseReason::WENT_AWAY:
return went_away_value;
}
NOTREACHED();
return error_value;
}
void ThrowPresentationDisconnectedError(ExceptionState& exception_state) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Presentation connection is disconnected.");
}
} // namespace
class PresentationConnection::Message final
: public GarbageCollectedFinalized<PresentationConnection::Message> {
public:
Message(const String& text) : type(kMessageTypeText), text(text) {}
Message(DOMArrayBuffer* array_buffer)
: type(kMessageTypeArrayBuffer), array_buffer(array_buffer) {}
Message(scoped_refptr<BlobDataHandle> blob_data_handle)
: type(kMessageTypeBlob), blob_data_handle(std::move(blob_data_handle)) {}
void Trace(blink::Visitor* visitor) { visitor->Trace(array_buffer); }
MessageType type;
String text;
Member<DOMArrayBuffer> array_buffer;
scoped_refptr<BlobDataHandle> blob_data_handle;
};
class PresentationConnection::BlobLoader final
: public GarbageCollectedFinalized<PresentationConnection::BlobLoader>,
public FileReaderLoaderClient {
public:
BlobLoader(scoped_refptr<BlobDataHandle> blob_data_handle,
PresentationConnection* presentation_connection)
: presentation_connection_(presentation_connection),
loader_(FileReaderLoader::Create(FileReaderLoader::kReadAsArrayBuffer,
this)) {
loader_->Start(std::move(blob_data_handle));
}
~BlobLoader() override = default;
// FileReaderLoaderClient functions.
void DidStartLoading() override {}
void DidReceiveData() override {}
void DidFinishLoading() override {
presentation_connection_->DidFinishLoadingBlob(
loader_->ArrayBufferResult());
}
void DidFail(FileErrorCode error_code) override {
presentation_connection_->DidFailLoadingBlob(error_code);
}
void Cancel() { loader_->Cancel(); }
void Trace(blink::Visitor* visitor) {
visitor->Trace(presentation_connection_);
}
private:
Member<PresentationConnection> presentation_connection_;
std::unique_ptr<FileReaderLoader> loader_;
};
PresentationConnection::PresentationConnection(LocalFrame& frame,
const String& id,
const KURL& url)
: ContextLifecycleObserver(frame.GetDocument()),
id_(id),
url_(url),
state_(mojom::blink::PresentationConnectionState::CONNECTING),
connection_binding_(this),
binary_type_(kBinaryTypeArrayBuffer) {}
PresentationConnection::~PresentationConnection() {
DCHECK(!blob_loader_);
}
void PresentationConnection::OnMessage(
mojom::blink::PresentationConnectionMessagePtr message) {
if (message->is_data()) {
const auto& data = message->get_data();
DidReceiveBinaryMessage(&data.front(), data.size());
} else {
DidReceiveTextMessage(message->get_message());
}
}
void PresentationConnection::DidChangeState(
mojom::blink::PresentationConnectionState state) {
// Closed state is handled in |DidClose()|.
DCHECK_NE(mojom::blink::PresentationConnectionState::CLOSED, state);
if (state_ == state)
return;
state_ = state;
switch (state_) {
case mojom::blink::PresentationConnectionState::CONNECTING:
return;
case mojom::blink::PresentationConnectionState::CONNECTED:
DispatchStateChangeEvent(Event::Create(event_type_names::kConnect));
return;
case mojom::blink::PresentationConnectionState::CLOSED:
return;
case mojom::blink::PresentationConnectionState::TERMINATED:
DispatchStateChangeEvent(Event::Create(event_type_names::kTerminate));
return;
}
NOTREACHED();
}
void PresentationConnection::DidClose(
mojom::blink::PresentationConnectionCloseReason reason) {
DidClose(reason, /* message */ String());
}
// static
ControllerPresentationConnection* ControllerPresentationConnection::Take(
ScriptPromiseResolver* resolver,
const mojom::blink::PresentationInfo& presentation_info,
PresentationRequest* request) {
DCHECK(resolver);
DCHECK(request);
Document* document = To<Document>(resolver->GetExecutionContext());
if (!document->GetFrame())
return nullptr;
PresentationController* controller =
PresentationController::From(*document->GetFrame());
if (!controller)
return nullptr;
return Take(controller, presentation_info, request);
}
// static
ControllerPresentationConnection* ControllerPresentationConnection::Take(
PresentationController* controller,
const mojom::blink::PresentationInfo& presentation_info,
PresentationRequest* request) {
DCHECK(controller);
DCHECK(request);
auto* connection = new ControllerPresentationConnection(
*controller->GetFrame(), controller, presentation_info.id,
presentation_info.url);
controller->RegisterConnection(connection);
// Fire onconnectionavailable event asynchronously.
auto* event = PresentationConnectionAvailableEvent::Create(
event_type_names::kConnectionavailable, connection);
request->GetExecutionContext()
->GetTaskRunner(TaskType::kPresentation)
->PostTask(FROM_HERE,
WTF::Bind(&PresentationConnection::DispatchEventAsync,
WrapPersistent(request), WrapPersistent(event)));
return connection;
}
ControllerPresentationConnection::ControllerPresentationConnection(
LocalFrame& frame,
PresentationController* controller,
const String& id,
const KURL& url)
: PresentationConnection(frame, id, url), controller_(controller) {}
ControllerPresentationConnection::~ControllerPresentationConnection() {}
void ControllerPresentationConnection::Trace(blink::Visitor* visitor) {
visitor->Trace(controller_);
PresentationConnection::Trace(visitor);
}
void ControllerPresentationConnection::Init(
mojom::blink::PresentationConnectionPtr connection_ptr,
mojom::blink::PresentationConnectionRequest connection_request) {
// Note that it is possible for the binding to be already bound here, because
// the ControllerPresentationConnection object could be reused when
// reconnecting in the same frame. In this case the existing connections are
// discarded.
if (connection_binding_.is_bound()) {
connection_binding_.Close();
target_connection_.reset();
}
DidChangeState(mojom::blink::PresentationConnectionState::CONNECTING);
target_connection_ = std::move(connection_ptr);
connection_binding_.Bind(std::move(connection_request));
}
void ControllerPresentationConnection::CloseInternal() {
auto& service = controller_->GetPresentationService();
if (service)
service->CloseConnection(url_, id_);
}
void ControllerPresentationConnection::TerminateInternal() {
auto& service = controller_->GetPresentationService();
if (service)
service->Terminate(url_, id_);
}
// static
ReceiverPresentationConnection* ReceiverPresentationConnection::Take(
PresentationReceiver* receiver,
const mojom::blink::PresentationInfo& presentation_info,
mojom::blink::PresentationConnectionPtr controller_connection,
mojom::blink::PresentationConnectionRequest receiver_connection_request) {
DCHECK(receiver);
ReceiverPresentationConnection* connection =
MakeGarbageCollected<ReceiverPresentationConnection>(
*receiver->GetFrame(), receiver, presentation_info.id,
presentation_info.url);
connection->Init(std::move(controller_connection),
std::move(receiver_connection_request));
receiver->RegisterConnection(connection);
return connection;
}
ReceiverPresentationConnection::ReceiverPresentationConnection(
LocalFrame& frame,
PresentationReceiver* receiver,
const String& id,
const KURL& url)
: PresentationConnection(frame, id, url), receiver_(receiver) {}
ReceiverPresentationConnection::~ReceiverPresentationConnection() = default;
void ReceiverPresentationConnection::Init(
mojom::blink::PresentationConnectionPtr controller_connection_ptr,
mojom::blink::PresentationConnectionRequest receiver_connection_request) {
target_connection_ = std::move(controller_connection_ptr);
connection_binding_.Bind(std::move(receiver_connection_request));
target_connection_->DidChangeState(
mojom::blink::PresentationConnectionState::CONNECTED);
DidChangeState(mojom::blink::PresentationConnectionState::CONNECTED);
}
void ReceiverPresentationConnection::DidChangeState(
mojom::blink::PresentationConnectionState state) {
PresentationConnection::DidChangeState(state);
}
void ReceiverPresentationConnection::DidClose(
mojom::blink::PresentationConnectionCloseReason reason) {
PresentationConnection::DidClose(reason);
receiver_->RemoveConnection(this);
}
void ReceiverPresentationConnection::CloseInternal() {
// No-op
}
void ReceiverPresentationConnection::TerminateInternal() {
// This will close the receiver window. Change the state to TERMINATED now
// since ReceiverPresentationConnection won't get a state change notification.
if (state_ == mojom::blink::PresentationConnectionState::TERMINATED)
return;
receiver_->Terminate();
state_ = mojom::blink::PresentationConnectionState::TERMINATED;
if (target_connection_)
target_connection_->DidChangeState(state_);
}
void ReceiverPresentationConnection::Trace(blink::Visitor* visitor) {
visitor->Trace(receiver_);
PresentationConnection::Trace(visitor);
}
const AtomicString& PresentationConnection::InterfaceName() const {
return event_target_names::kPresentationConnection;
}
ExecutionContext* PresentationConnection::GetExecutionContext() const {
if (!GetFrame())
return nullptr;
return GetFrame()->GetDocument();
}
void PresentationConnection::AddedEventListener(
const AtomicString& event_type,
RegisteredEventListener& registered_listener) {
EventTargetWithInlineData::AddedEventListener(event_type,
registered_listener);
if (event_type == event_type_names::kConnect) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kPresentationConnectionConnectEventListener);
} else if (event_type == event_type_names::kClose) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kPresentationConnectionCloseEventListener);
} else if (event_type == event_type_names::kTerminate) {
UseCounter::Count(
GetExecutionContext(),
WebFeature::kPresentationConnectionTerminateEventListener);
} else if (event_type == event_type_names::kMessage) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kPresentationConnectionMessageEventListener);
}
}
void PresentationConnection::ContextDestroyed(ExecutionContext*) {
DoClose(mojom::blink::PresentationConnectionCloseReason::WENT_AWAY);
target_connection_.reset();
connection_binding_.Close();
}
void PresentationConnection::Trace(blink::Visitor* visitor) {
visitor->Trace(blob_loader_);
visitor->Trace(messages_);
EventTargetWithInlineData::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
}
const AtomicString& PresentationConnection::state() const {
return ConnectionStateToString(state_);
}
void PresentationConnection::send(const String& message,
ExceptionState& exception_state) {
if (!CanSendMessage(exception_state))
return;
messages_.push_back(MakeGarbageCollected<Message>(message));
HandleMessageQueue();
}
void PresentationConnection::send(DOMArrayBuffer* array_buffer,
ExceptionState& exception_state) {
DCHECK(array_buffer);
DCHECK(array_buffer->Buffer());
if (!CanSendMessage(exception_state))
return;
messages_.push_back(MakeGarbageCollected<Message>(array_buffer));
HandleMessageQueue();
}
void PresentationConnection::send(
NotShared<DOMArrayBufferView> array_buffer_view,
ExceptionState& exception_state) {
DCHECK(array_buffer_view);
if (!CanSendMessage(exception_state))
return;
messages_.push_back(
MakeGarbageCollected<Message>(array_buffer_view.View()->buffer()));
HandleMessageQueue();
}
void PresentationConnection::send(Blob* data, ExceptionState& exception_state) {
DCHECK(data);
if (!CanSendMessage(exception_state))
return;
messages_.push_back(MakeGarbageCollected<Message>(data->GetBlobDataHandle()));
HandleMessageQueue();
}
void PresentationConnection::DoClose(
mojom::blink::PresentationConnectionCloseReason reason) {
if (state_ != mojom::blink::PresentationConnectionState::CONNECTING &&
state_ != mojom::blink::PresentationConnectionState::CONNECTED) {
return;
}
if (target_connection_)
target_connection_->DidClose(reason);
DidClose(reason);
CloseInternal();
TearDown();
}
bool PresentationConnection::CanSendMessage(ExceptionState& exception_state) {
if (state_ != mojom::blink::PresentationConnectionState::CONNECTED) {
ThrowPresentationDisconnectedError(exception_state);
return false;
}
return !!target_connection_;
}
void PresentationConnection::HandleMessageQueue() {
if (!target_connection_)
return;
while (!messages_.IsEmpty() && !blob_loader_) {
Message* message = messages_.front().Get();
switch (message->type) {
case kMessageTypeText:
SendMessageToTargetConnection(MakeTextMessage(message->text));
messages_.pop_front();
break;
case kMessageTypeArrayBuffer:
SendMessageToTargetConnection(MakeBinaryMessage(message->array_buffer));
messages_.pop_front();
break;
case kMessageTypeBlob:
DCHECK(!blob_loader_);
blob_loader_ =
MakeGarbageCollected<BlobLoader>(message->blob_data_handle, this);
break;
}
}
}
String PresentationConnection::binaryType() const {
switch (binary_type_) {
case kBinaryTypeBlob:
return "blob";
case kBinaryTypeArrayBuffer:
return "arraybuffer";
}
NOTREACHED();
return String();
}
void PresentationConnection::setBinaryType(const String& binary_type) {
if (binary_type == "blob") {
binary_type_ = kBinaryTypeBlob;
return;
}
if (binary_type == "arraybuffer") {
binary_type_ = kBinaryTypeArrayBuffer;
return;
}
NOTREACHED();
}
void PresentationConnection::SendMessageToTargetConnection(
mojom::blink::PresentationConnectionMessagePtr message) {
if (target_connection_)
target_connection_->OnMessage(std::move(message));
}
void PresentationConnection::DidReceiveTextMessage(const WebString& message) {
if (state_ != mojom::blink::PresentationConnectionState::CONNECTED)
return;
DispatchEvent(*MessageEvent::Create(message));
}
void PresentationConnection::DidReceiveBinaryMessage(const uint8_t* data,
uint32_t length) {
if (state_ != mojom::blink::PresentationConnectionState::CONNECTED)
return;
switch (binary_type_) {
case kBinaryTypeBlob: {
std::unique_ptr<BlobData> blob_data = BlobData::Create();
blob_data->AppendBytes(data, length);
Blob* blob =
Blob::Create(BlobDataHandle::Create(std::move(blob_data), length));
DispatchEvent(*MessageEvent::Create(blob));
return;
}
case kBinaryTypeArrayBuffer:
DOMArrayBuffer* buffer = DOMArrayBuffer::Create(data, length);
DispatchEvent(*MessageEvent::Create(buffer));
return;
}
NOTREACHED();
}
mojom::blink::PresentationConnectionState PresentationConnection::GetState()
const {
return state_;
}
void PresentationConnection::close() {
DoClose(mojom::blink::PresentationConnectionCloseReason::CLOSED);
}
void PresentationConnection::terminate() {
if (state_ != mojom::blink::PresentationConnectionState::CONNECTED)
return;
TerminateInternal();
TearDown();
}
bool PresentationConnection::Matches(const String& id, const KURL& url) const {
return url_ == url && id_ == id;
}
void PresentationConnection::DidClose(
mojom::blink::PresentationConnectionCloseReason reason,
const String& message) {
if (state_ == mojom::blink::PresentationConnectionState::CLOSED ||
state_ == mojom::blink::PresentationConnectionState::TERMINATED) {
return;
}
state_ = mojom::blink::PresentationConnectionState::CLOSED;
DispatchStateChangeEvent(PresentationConnectionCloseEvent::Create(
event_type_names::kClose, ConnectionCloseReasonToString(reason),
message));
}
void PresentationConnection::DidFinishLoadingBlob(DOMArrayBuffer* buffer) {
DCHECK(!messages_.IsEmpty());
DCHECK_EQ(messages_.front()->type, kMessageTypeBlob);
DCHECK(buffer);
DCHECK(buffer->Buffer());
// Send the loaded blob immediately here and continue processing the queue.
SendMessageToTargetConnection(MakeBinaryMessage(buffer));
messages_.pop_front();
blob_loader_.Clear();
HandleMessageQueue();
}
void PresentationConnection::DidFailLoadingBlob(FileErrorCode error_code) {
DCHECK(!messages_.IsEmpty());
DCHECK_EQ(messages_.front()->type, kMessageTypeBlob);
// FIXME: generate error message?
// Ignore the current failed blob item and continue with next items.
messages_.pop_front();
blob_loader_.Clear();
HandleMessageQueue();
}
void PresentationConnection::DispatchStateChangeEvent(Event* event) {
GetExecutionContext()
->GetTaskRunner(TaskType::kPresentation)
->PostTask(FROM_HERE,
WTF::Bind(&PresentationConnection::DispatchEventAsync,
WrapPersistent(this), WrapPersistent(event)));
}
// static
void PresentationConnection::DispatchEventAsync(EventTarget* target,
Event* event) {
DCHECK(target);
DCHECK(event);
target->DispatchEvent(*event);
}
void PresentationConnection::TearDown() {
// Cancel current Blob loading if any.
if (blob_loader_) {
blob_loader_->Cancel();
blob_loader_.Clear();
}
messages_.clear();
}
} // namespace blink