blob: dba1c16a5bf42b4bfe1bda99b9510c5b288f7912 [file] [log] [blame]
// Copyright 2015 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/bluetooth/bluetooth_remote_gatt_server.h"
#include <utility>
#include "mojo/public/cpp/bindings/associated_receiver_set.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/callback_promise_adapter.h"
#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/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/modules/bluetooth/bluetooth.h"
#include "third_party/blink/renderer/modules/bluetooth/bluetooth_device.h"
#include "third_party/blink/renderer/modules/bluetooth/bluetooth_error.h"
#include "third_party/blink/renderer/modules/bluetooth/bluetooth_remote_gatt_service.h"
#include "third_party/blink/renderer/modules/bluetooth/bluetooth_uuid.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
BluetoothRemoteGATTServer::BluetoothRemoteGATTServer(ExecutionContext* context,
BluetoothDevice* device)
: ExecutionContextLifecycleObserver(context),
device_(device),
connected_(false) {}
void BluetoothRemoteGATTServer::ContextDestroyed() {
Dispose();
}
void BluetoothRemoteGATTServer::GATTServerDisconnected() {
DispatchDisconnected();
}
void BluetoothRemoteGATTServer::AddToActiveAlgorithms(
ScriptPromiseResolver* resolver) {
auto result = active_algorithms_.insert(resolver);
CHECK(result.is_new_entry);
}
bool BluetoothRemoteGATTServer::RemoveFromActiveAlgorithms(
ScriptPromiseResolver* resolver) {
if (!active_algorithms_.Contains(resolver)) {
return false;
}
active_algorithms_.erase(resolver);
return true;
}
void BluetoothRemoteGATTServer::DisconnectIfConnected() {
if (connected_) {
SetConnected(false);
ClearActiveAlgorithms();
mojom::blink::WebBluetoothService* service =
device_->GetBluetooth()->Service();
service->RemoteServerDisconnect(device_->id());
}
}
void BluetoothRemoteGATTServer::CleanupDisconnectedDeviceAndFireEvent() {
DCHECK(connected_);
SetConnected(false);
ClearActiveAlgorithms();
device_->ClearAttributeInstanceMapAndFireEvent();
}
void BluetoothRemoteGATTServer::DispatchDisconnected() {
if (!connected_) {
return;
}
CleanupDisconnectedDeviceAndFireEvent();
}
void BluetoothRemoteGATTServer::Dispose() {
DisconnectIfConnected();
// The pipe to this object must be closed when is marked unreachable to
// prevent messages from being dispatched before lazy sweeping.
client_receivers_.Clear();
}
void BluetoothRemoteGATTServer::Trace(Visitor* visitor) {
visitor->Trace(active_algorithms_);
visitor->Trace(device_);
ScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
}
void BluetoothRemoteGATTServer::ConnectCallback(
ScriptPromiseResolver* resolver,
mojom::blink::WebBluetoothResult result) {
if (!resolver->GetExecutionContext() ||
resolver->GetExecutionContext()->IsContextDestroyed())
return;
if (result == mojom::blink::WebBluetoothResult::SUCCESS) {
SetConnected(true);
resolver->Resolve(this);
} else {
resolver->Reject(BluetoothError::CreateDOMException(result));
}
}
ScriptPromise BluetoothRemoteGATTServer::connect(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
mojom::blink::WebBluetoothService* service =
device_->GetBluetooth()->Service();
mojo::PendingAssociatedRemote<mojom::blink::WebBluetoothServerClient> client;
// See https://bit.ly/2S0zRAS for task types.
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI);
client_receivers_.Add(this, client.InitWithNewEndpointAndPassReceiver(),
std::move(task_runner));
service->RemoteServerConnect(
device_->id(), std::move(client),
WTF::Bind(&BluetoothRemoteGATTServer::ConnectCallback,
WrapPersistent(this), WrapPersistent(resolver)));
return promise;
}
void BluetoothRemoteGATTServer::disconnect(ScriptState* script_state) {
if (!connected_)
return;
CleanupDisconnectedDeviceAndFireEvent();
client_receivers_.Clear();
mojom::blink::WebBluetoothService* service =
device_->GetBluetooth()->Service();
service->RemoteServerDisconnect(device_->id());
}
// Callback that allows us to resolve the promise with a single service or
// with a vector owning the services.
void BluetoothRemoteGATTServer::GetPrimaryServicesCallback(
const String& requested_service_uuid,
mojom::blink::WebBluetoothGATTQueryQuantity quantity,
ScriptPromiseResolver* resolver,
mojom::blink::WebBluetoothResult result,
base::Optional<Vector<mojom::blink::WebBluetoothRemoteGATTServicePtr>>
services) {
if (!resolver->GetExecutionContext() ||
resolver->GetExecutionContext()->IsContextDestroyed())
return;
// If the device is disconnected, reject.
if (!RemoveFromActiveAlgorithms(resolver)) {
resolver->Reject(BluetoothError::CreateNotConnectedException(
BluetoothOperation::kServicesRetrieval));
return;
}
if (result == mojom::blink::WebBluetoothResult::SUCCESS) {
DCHECK(services);
if (quantity == mojom::blink::WebBluetoothGATTQueryQuantity::SINGLE) {
DCHECK_EQ(1u, services->size());
resolver->Resolve(device_->GetOrCreateRemoteGATTService(
std::move(services.value()[0]), true /* isPrimary */, device_->id()));
return;
}
HeapVector<Member<BluetoothRemoteGATTService>> gatt_services;
gatt_services.ReserveInitialCapacity(services->size());
for (auto& service : services.value()) {
gatt_services.push_back(device_->GetOrCreateRemoteGATTService(
std::move(service), true /* isPrimary */, device_->id()));
}
resolver->Resolve(gatt_services);
} else {
if (result == mojom::blink::WebBluetoothResult::SERVICE_NOT_FOUND) {
resolver->Reject(BluetoothError::CreateDOMException(
BluetoothErrorCode::kServiceNotFound, "No Services matching UUID " +
requested_service_uuid +
" found in Device."));
} else {
resolver->Reject(BluetoothError::CreateDOMException(result));
}
}
}
ScriptPromise BluetoothRemoteGATTServer::getPrimaryService(
ScriptState* script_state,
const StringOrUnsignedLong& service,
ExceptionState& exception_state) {
String service_uuid = BluetoothUUID::getService(service, exception_state);
if (exception_state.HadException())
return ScriptPromise();
return GetPrimaryServicesImpl(
script_state, exception_state,
mojom::blink::WebBluetoothGATTQueryQuantity::SINGLE, service_uuid);
}
ScriptPromise BluetoothRemoteGATTServer::getPrimaryServices(
ScriptState* script_state,
const StringOrUnsignedLong& service,
ExceptionState& exception_state) {
String service_uuid = BluetoothUUID::getService(service, exception_state);
if (exception_state.HadException())
return ScriptPromise();
return GetPrimaryServicesImpl(
script_state, exception_state,
mojom::blink::WebBluetoothGATTQueryQuantity::MULTIPLE, service_uuid);
}
ScriptPromise BluetoothRemoteGATTServer::getPrimaryServices(
ScriptState* script_state,
ExceptionState& exception_state) {
return GetPrimaryServicesImpl(
script_state, exception_state,
mojom::blink::WebBluetoothGATTQueryQuantity::MULTIPLE);
}
ScriptPromise BluetoothRemoteGATTServer::GetPrimaryServicesImpl(
ScriptState* script_state,
ExceptionState& exception_state,
mojom::blink::WebBluetoothGATTQueryQuantity quantity,
String services_uuid) {
if (!connected_) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNetworkError,
BluetoothError::CreateNotConnectedExceptionMessage(
BluetoothOperation::kServicesRetrieval));
return ScriptPromise();
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
AddToActiveAlgorithms(resolver);
mojom::blink::WebBluetoothService* service =
device_->GetBluetooth()->Service();
service->RemoteServerGetPrimaryServices(
device_->id(), quantity, services_uuid,
WTF::Bind(&BluetoothRemoteGATTServer::GetPrimaryServicesCallback,
WrapPersistent(this), services_uuid, quantity,
WrapPersistent(resolver)));
return promise;
}
} // namespace blink