blob: ff9f7b6cfe3ca4132c1095f57dc37ca3dc77f101 [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 "modules/presentation/PresentationConnection.h"
#include <memory>
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/events/Event.h"
#include "core/events/MessageEvent.h"
#include "core/fileapi/FileReaderLoader.h"
#include "core/fileapi/FileReaderLoaderClient.h"
#include "core/frame/Deprecation.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/typed_arrays/DOMArrayBuffer.h"
#include "core/typed_arrays/DOMArrayBufferView.h"
#include "modules/EventTargetModules.h"
#include "modules/presentation/Presentation.h"
#include "modules/presentation/PresentationConnectionAvailableEvent.h"
#include "modules/presentation/PresentationConnectionCloseEvent.h"
#include "modules/presentation/PresentationController.h"
#include "modules/presentation/PresentationReceiver.h"
#include "modules/presentation/PresentationRequest.h"
#include "platform/wtf/Assertions.h"
#include "platform/wtf/text/AtomicString.h"
#include "public/platform/TaskType.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(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(presentation_connection_->GetExecutionContext(),
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(FileError::ErrorCode 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,
OnMessageCallback callback) {
DCHECK(!callback.is_null());
if (message->is_data()) {
const auto& data = message->get_data();
DidReceiveBinaryMessage(&data.front(), data.size());
} else {
DidReceiveTextMessage(message->get_message());
}
std::move(callback).Run(true);
}
void PresentationConnection::DidChangeState(
mojom::blink::PresentationConnectionState state) {
if (state_ == state)
return;
if (state == mojom::blink::PresentationConnectionState::CLOSED) {
DidClose();
return;
}
state_ = state;
switch (state_) {
case mojom::blink::PresentationConnectionState::CONNECTING:
return;
case mojom::blink::PresentationConnectionState::CONNECTED:
DispatchStateChangeEvent(Event::Create(EventTypeNames::connect));
return;
// Closed state is handled in |DidClose()|.
case mojom::blink::PresentationConnectionState::CLOSED:
return;
case mojom::blink::PresentationConnectionState::TERMINATED:
DispatchStateChangeEvent(Event::Create(EventTypeNames::terminate));
return;
}
NOTREACHED();
}
void PresentationConnection::RequestClose() {
DidChangeState(mojom::blink::PresentationConnectionState::CLOSED);
// TODO(crbug.com/749327): Instead of calling DidChangeState, consider
// supplying a callback to RequestClose() and invoking it here.
if (target_connection_) {
target_connection_->DidChangeState(
mojom::blink::PresentationConnectionState::CLOSED);
}
}
// static
ControllerPresentationConnection* ControllerPresentationConnection::Take(
ScriptPromiseResolver* resolver,
const mojom::blink::PresentationInfo& presentation_info,
PresentationRequest* request) {
DCHECK(resolver);
DCHECK(request);
DCHECK(resolver->GetExecutionContext()->IsDocument());
Document* document = ToDocument(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(
EventTypeNames::connectionavailable, 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() = default;
void ControllerPresentationConnection::Trace(blink::Visitor* visitor) {
visitor->Trace(controller_);
PresentationConnection::Trace(visitor);
}
void ControllerPresentationConnection::Init() {
// 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();
}
mojom::blink::PresentationConnectionPtr controller_connection_ptr;
connection_binding_.Bind(mojo::MakeRequest(&controller_connection_ptr));
auto& service = controller_->GetPresentationService();
if (service) {
service->SetPresentationConnection(
mojom::blink::PresentationInfo::New(url_, id_),
std::move(controller_connection_ptr),
mojo::MakeRequest(&target_connection_));
}
}
void ControllerPresentationConnection::DoClose() {
auto& service = controller_->GetPresentationService();
if (service)
service->CloseConnection(url_, id_);
}
void ControllerPresentationConnection::DoTerminate() {
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 =
new 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);
if (state == mojom::blink::PresentationConnectionState::CLOSED)
receiver_->RemoveConnection(this);
}
void ReceiverPresentationConnection::OnReceiverTerminated() {
// We don't invoke PresentationConnection::DidChangeState here because we do
// not want to dispatch an event, as the page might be closing.
state_ = mojom::blink::PresentationConnectionState::TERMINATED;
if (target_connection_)
target_connection_->DidChangeState(state_);
}
void ReceiverPresentationConnection::DoClose() {
// No-op
}
void ReceiverPresentationConnection::DoTerminate() {
// This will close the receiver window. At some point later
// OnReceiverTerminated() will be invoked.
receiver_->Terminate();
}
void ReceiverPresentationConnection::Trace(blink::Visitor* visitor) {
visitor->Trace(receiver_);
PresentationConnection::Trace(visitor);
}
const AtomicString& PresentationConnection::InterfaceName() const {
return EventTargetNames::PresentationConnection;
}
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 == EventTypeNames::connect) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kPresentationConnectionConnectEventListener);
} else if (event_type == EventTypeNames::close) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kPresentationConnectionCloseEventListener);
} else if (event_type == EventTypeNames::terminate) {
UseCounter::Count(
GetExecutionContext(),
WebFeature::kPresentationConnectionTerminateEventListener);
} else if (event_type == EventTypeNames::message) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kPresentationConnectionMessageEventListener);
}
}
void PresentationConnection::ContextDestroyed(ExecutionContext*) {
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(new 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(new 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(new 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(new Message(data->GetBlobDataHandle()));
HandleMessageQueue();
}
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_ = new 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),
base::OnceCallback<void(bool)>());
}
}
void PresentationConnection::DidReceiveTextMessage(const WebString& message) {
if (state_ != mojom::blink::PresentationConnectionState::CONNECTED)
return;
DispatchEvent(MessageEvent::Create(message));
}
void PresentationConnection::DidReceiveBinaryMessage(const uint8_t* data,
size_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() {
if (state_ != mojom::blink::PresentationConnectionState::CONNECTING &&
state_ != mojom::blink::PresentationConnectionState::CONNECTED) {
return;
}
if (target_connection_)
target_connection_->RequestClose();
DoClose();
TearDown();
}
void PresentationConnection::terminate() {
if (state_ != mojom::blink::PresentationConnectionState::CONNECTED)
return;
DoTerminate();
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(
EventTypeNames::close, ConnectionCloseReasonToString(reason), message));
}
void PresentationConnection::DidClose() {
DidClose(mojom::blink::PresentationConnectionCloseReason::CLOSED, "");
}
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(
FileError::ErrorCode 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