blob: f19b0a7cf1aa7ce074d8df16928af85f6a7bcd0c [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 "modules/bluetooth/BluetoothRemoteGATTCharacteristic.h"
#include "bindings/core/v8/ScriptPromise.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/dom/DOMDataView.h"
#include "core/dom/DOMException.h"
#include "core/dom/ExceptionCode.h"
#include "core/events/Event.h"
#include "core/inspector/ConsoleMessage.h"
#include "modules/bluetooth/BluetoothCharacteristicProperties.h"
#include "modules/bluetooth/BluetoothError.h"
#include "modules/bluetooth/BluetoothRemoteGATTService.h"
#include "modules/bluetooth/BluetoothSupplement.h"
#include "public/platform/modules/bluetooth/WebBluetooth.h"
#include <memory>
namespace blink {
namespace {
const char kGATTServerDisconnected[] =
"GATT Server disconnected while performing a GATT operation.";
const char kGATTServerNotConnected[] =
"GATT Server is disconnected. Cannot perform GATT operations.";
const char kInvalidCharacteristic[] =
"Characteristic is no longer valid. Remember to retrieve the "
"characteristic again after reconnecting.";
DOMDataView* ConvertWebVectorToDataView(const WebVector<uint8_t>& webVector) {
static_assert(sizeof(*webVector.data()) == 1,
"uint8_t should be a single byte");
DOMArrayBuffer* domBuffer =
DOMArrayBuffer::create(webVector.data(), webVector.size());
return DOMDataView::create(domBuffer, 0, webVector.size());
}
} // anonymous namespace
BluetoothRemoteGATTCharacteristic::BluetoothRemoteGATTCharacteristic(
ExecutionContext* context,
std::unique_ptr<WebBluetoothRemoteGATTCharacteristicInit> webCharacteristic,
BluetoothRemoteGATTService* service)
: ActiveDOMObject(context),
m_webCharacteristic(std::move(webCharacteristic)),
m_service(service),
m_stopped(false) {
m_properties = BluetoothCharacteristicProperties::create(
m_webCharacteristic->characteristicProperties);
// See example in Source/platform/heap/ThreadState.h
ThreadState::current()->registerPreFinalizer(this);
}
BluetoothRemoteGATTCharacteristic* BluetoothRemoteGATTCharacteristic::create(
ExecutionContext* context,
std::unique_ptr<WebBluetoothRemoteGATTCharacteristicInit> webCharacteristic,
BluetoothRemoteGATTService* service) {
DCHECK(webCharacteristic);
BluetoothRemoteGATTCharacteristic* characteristic =
new BluetoothRemoteGATTCharacteristic(
context, std::move(webCharacteristic), service);
// See note in ActiveDOMObject about suspendIfNeeded.
characteristic->suspendIfNeeded();
return characteristic;
}
void BluetoothRemoteGATTCharacteristic::setValue(DOMDataView* domDataView) {
m_value = domDataView;
}
void BluetoothRemoteGATTCharacteristic::dispatchCharacteristicValueChanged(
const WebVector<uint8_t>& value) {
this->setValue(ConvertWebVectorToDataView(value));
dispatchEvent(Event::create(EventTypeNames::characteristicvaluechanged));
}
void BluetoothRemoteGATTCharacteristic::contextDestroyed() {
notifyCharacteristicObjectRemoved();
}
void BluetoothRemoteGATTCharacteristic::dispose() {
notifyCharacteristicObjectRemoved();
}
void BluetoothRemoteGATTCharacteristic::notifyCharacteristicObjectRemoved() {
if (!m_stopped) {
m_stopped = true;
WebBluetooth* webbluetooth = BluetoothSupplement::fromExecutionContext(
ActiveDOMObject::getExecutionContext());
webbluetooth->characteristicObjectRemoved(
m_webCharacteristic->characteristicInstanceID, this);
}
}
const WTF::AtomicString& BluetoothRemoteGATTCharacteristic::interfaceName()
const {
return EventTargetNames::BluetoothRemoteGATTCharacteristic;
}
ExecutionContext* BluetoothRemoteGATTCharacteristic::getExecutionContext()
const {
return ActiveDOMObject::getExecutionContext();
}
void BluetoothRemoteGATTCharacteristic::addedEventListener(
const AtomicString& eventType,
RegisteredEventListener& registeredListener) {
EventTargetWithInlineData::addedEventListener(eventType, registeredListener);
// We will also need to unregister a characteristic once all the event
// listeners have been removed. See http://crbug.com/541390
if (eventType == EventTypeNames::characteristicvaluechanged) {
WebBluetooth* webbluetooth =
BluetoothSupplement::fromExecutionContext(getExecutionContext());
webbluetooth->registerCharacteristicObject(
m_webCharacteristic->characteristicInstanceID, this);
}
}
class ReadValueCallback : public WebBluetoothReadValueCallbacks {
public:
ReadValueCallback(BluetoothRemoteGATTCharacteristic* characteristic,
ScriptPromiseResolver* resolver)
: m_characteristic(characteristic), m_resolver(resolver) {
// We always check that the device is connected before constructing this
// object.
CHECK(m_characteristic->gatt()->connected());
m_characteristic->gatt()->AddToActiveAlgorithms(m_resolver.get());
}
void onSuccess(const WebVector<uint8_t>& value) override {
if (!m_resolver->getExecutionContext() ||
m_resolver->getExecutionContext()->isContextDestroyed())
return;
if (!m_characteristic->gatt()->RemoveFromActiveAlgorithms(
m_resolver.get())) {
m_resolver->reject(
DOMException::create(NetworkError, kGATTServerDisconnected));
return;
}
DOMDataView* domDataView = ConvertWebVectorToDataView(value);
if (m_characteristic)
m_characteristic->setValue(domDataView);
m_resolver->resolve(domDataView);
}
void onError(
int32_t
error /* Corresponds to WebBluetoothResult in web_bluetooth.mojom */)
override {
if (!m_resolver->getExecutionContext() ||
m_resolver->getExecutionContext()->isContextDestroyed())
return;
if (!m_characteristic->gatt()->RemoveFromActiveAlgorithms(
m_resolver.get())) {
m_resolver->reject(
DOMException::create(NetworkError, kGATTServerDisconnected));
return;
}
m_resolver->reject(BluetoothError::take(m_resolver, error));
}
private:
Persistent<BluetoothRemoteGATTCharacteristic> m_characteristic;
Persistent<ScriptPromiseResolver> m_resolver;
};
ScriptPromise BluetoothRemoteGATTCharacteristic::readValue(
ScriptState* scriptState) {
if (!gatt()->connected()) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(NetworkError, kGATTServerNotConnected));
}
if (!gatt()->device()->isValidCharacteristic(
m_webCharacteristic->characteristicInstanceID)) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(InvalidStateError, kInvalidCharacteristic));
}
WebBluetooth* webbluetooth =
BluetoothSupplement::fromScriptState(scriptState);
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
ScriptPromise promise = resolver->promise();
webbluetooth->readValue(m_webCharacteristic->characteristicInstanceID,
new ReadValueCallback(this, resolver));
return promise;
}
class WriteValueCallback : public WebBluetoothWriteValueCallbacks {
public:
WriteValueCallback(BluetoothRemoteGATTCharacteristic* characteristic,
ScriptPromiseResolver* resolver)
: m_characteristic(characteristic), m_resolver(resolver) {
// We always check that the device is connected before constructing this
// object.
CHECK(m_characteristic->gatt()->connected());
m_characteristic->gatt()->AddToActiveAlgorithms(m_resolver.get());
}
void onSuccess(const WebVector<uint8_t>& value) override {
if (!m_resolver->getExecutionContext() ||
m_resolver->getExecutionContext()->isContextDestroyed())
return;
if (!m_characteristic->gatt()->RemoveFromActiveAlgorithms(
m_resolver.get())) {
m_resolver->reject(
DOMException::create(NetworkError, kGATTServerDisconnected));
return;
}
if (m_characteristic) {
m_characteristic->setValue(ConvertWebVectorToDataView(value));
}
m_resolver->resolve();
}
void onError(
int32_t
error /* Corresponds to WebBluetoothResult in web_bluetooth.mojom */)
override {
if (!m_resolver->getExecutionContext() ||
m_resolver->getExecutionContext()->isContextDestroyed())
return;
if (!m_characteristic->gatt()->RemoveFromActiveAlgorithms(
m_resolver.get())) {
m_resolver->reject(
DOMException::create(NetworkError, kGATTServerDisconnected));
return;
}
m_resolver->reject(BluetoothError::take(m_resolver, error));
}
private:
Persistent<BluetoothRemoteGATTCharacteristic> m_characteristic;
Persistent<ScriptPromiseResolver> m_resolver;
};
ScriptPromise BluetoothRemoteGATTCharacteristic::writeValue(
ScriptState* scriptState,
const DOMArrayPiece& value) {
if (!gatt()->connected()) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(NetworkError, kGATTServerNotConnected));
}
if (!gatt()->device()->isValidCharacteristic(
m_webCharacteristic->characteristicInstanceID)) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(InvalidStateError, kInvalidCharacteristic));
}
WebBluetooth* webbluetooth =
BluetoothSupplement::fromScriptState(scriptState);
// Partial implementation of writeValue algorithm:
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
// If bytes is more than 512 bytes long (the maximum length of an attribute
// value, per Long Attribute Values) return a promise rejected with an
// InvalidModificationError and abort.
if (value.byteLength() > 512)
return ScriptPromise::rejectWithDOMException(
scriptState, DOMException::create(InvalidModificationError,
"Value can't exceed 512 bytes."));
// Let valueVector be a copy of the bytes held by value.
WebVector<uint8_t> valueVector(value.bytes(), value.byteLength());
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
ScriptPromise promise = resolver->promise();
webbluetooth->writeValue(m_webCharacteristic->characteristicInstanceID,
valueVector, new WriteValueCallback(this, resolver));
return promise;
}
class NotificationsCallback : public WebBluetoothNotificationsCallbacks {
public:
NotificationsCallback(BluetoothRemoteGATTCharacteristic* characteristic,
ScriptPromiseResolver* resolver)
: m_characteristic(characteristic), m_resolver(resolver) {
// We always check that the device is connected before constructing this
// object.
CHECK(m_characteristic->gatt()->connected());
m_characteristic->gatt()->AddToActiveAlgorithms(m_resolver.get());
}
void onSuccess() override {
if (!m_resolver->getExecutionContext() ||
m_resolver->getExecutionContext()->isContextDestroyed())
return;
if (!m_characteristic->gatt()->RemoveFromActiveAlgorithms(
m_resolver.get())) {
m_resolver->reject(
DOMException::create(NetworkError, kGATTServerDisconnected));
return;
}
m_resolver->resolve(m_characteristic);
}
void onError(
int32_t
error /* Corresponds to WebBluetoothResult in web_bluetooth.mojom */)
override {
if (!m_resolver->getExecutionContext() ||
m_resolver->getExecutionContext()->isContextDestroyed())
return;
if (!m_characteristic->gatt()->RemoveFromActiveAlgorithms(
m_resolver.get())) {
m_resolver->reject(
DOMException::create(NetworkError, kGATTServerDisconnected));
return;
}
m_resolver->reject(BluetoothError::take(m_resolver, error));
}
private:
Persistent<BluetoothRemoteGATTCharacteristic> m_characteristic;
Persistent<ScriptPromiseResolver> m_resolver;
};
ScriptPromise BluetoothRemoteGATTCharacteristic::startNotifications(
ScriptState* scriptState) {
if (!gatt()->connected()) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(NetworkError, kGATTServerNotConnected));
}
if (!gatt()->device()->isValidCharacteristic(
m_webCharacteristic->characteristicInstanceID)) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(InvalidStateError, kInvalidCharacteristic));
}
WebBluetooth* webbluetooth =
BluetoothSupplement::fromScriptState(scriptState);
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
ScriptPromise promise = resolver->promise();
webbluetooth->startNotifications(
m_webCharacteristic->characteristicInstanceID,
new NotificationsCallback(this, resolver));
return promise;
}
ScriptPromise BluetoothRemoteGATTCharacteristic::stopNotifications(
ScriptState* scriptState) {
#if OS(MACOSX)
// TODO(jlebel): Remove when stopNotifications is implemented.
return ScriptPromise::rejectWithDOMException(
scriptState, DOMException::create(NotSupportedError,
"stopNotifications is not implemented "
"yet. See https://goo.gl/J6ASzs"));
#endif // OS(MACOSX)
if (!gatt()->connected()) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(NetworkError, kGATTServerNotConnected));
}
if (!gatt()->device()->isValidCharacteristic(
m_webCharacteristic->characteristicInstanceID)) {
return ScriptPromise::rejectWithDOMException(
scriptState,
DOMException::create(InvalidStateError, kInvalidCharacteristic));
}
WebBluetooth* webbluetooth =
BluetoothSupplement::fromScriptState(scriptState);
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
ScriptPromise promise = resolver->promise();
webbluetooth->stopNotifications(m_webCharacteristic->characteristicInstanceID,
new NotificationsCallback(this, resolver));
return promise;
}
DEFINE_TRACE(BluetoothRemoteGATTCharacteristic) {
visitor->trace(m_service);
visitor->trace(m_properties);
visitor->trace(m_value);
EventTargetWithInlineData::trace(visitor);
ActiveDOMObject::trace(visitor);
}
} // namespace blink